RSA加密、解密、簽名、驗簽的原理及方法

一、RSA加密簡介

RSA加密是一種非對稱加密??梢栽诓恢苯觽鬟f密鑰的情況下,完成解密。這能夠確保信息的安全性,避免了直接傳遞密鑰所造成的被破解的風(fēng)險。是由一對密鑰來進行加解密的過程,分別稱為公鑰和私鑰。兩者之間有數(shù)學(xué)相關(guān),該加密算法的原理就是對一極大整數(shù)做因數(shù)分解的困難性來保證安全性。通常個人保存私鑰,公鑰是公開的(可能同時多人持有)。

二、RSA加密、簽名區(qū)別

加密和簽名都是為了安全性考慮,但略有不同。常有人問加密和簽名是用私鑰還是公鑰?其實都是對加密和簽名的作用有所混淆。簡單的說,加密是為了防止信息被泄露,而簽名是為了防止信息被篡改。這里舉2個例子說明。

第一個場景:戰(zhàn)場上,B要給A傳遞一條消息,內(nèi)容為某一指令。

RSA的加密過程如下:

  • 1、A生成一對密鑰(公鑰和私鑰),私鑰不公開,A自己保留。公鑰為公開的,任何人可以獲取。

  • 2、A傳遞自己的公鑰給B,B用A的公鑰對消息進行加密。

  • 3、A接收到B加密的消息,利用A自己的私鑰對消息進行解密。

在這個過程中,只有2次傳遞過程,第一次是A傳遞公鑰給B,第二次是B傳遞加密消息給A,即使都被敵方截獲,也沒有危險性,因為只有A的私鑰才能對消息進行解密,防止了消息內(nèi)容的泄露。

第二個場景:A收到B發(fā)的消息后,需要進行回復(fù)“收到”。

RSA簽名的過程如下:

  • 1、A生成一對密鑰(公鑰和私鑰),私鑰不公開,A自己保留。公鑰為公開的,任何人可以獲取。

  • 2、A用自己的私鑰對消息加簽,形成簽名,并將加簽的消息和消息本身一起傳遞給B。

  • 3、B收到消息后,在獲取A的公鑰進行驗簽,如果驗簽出來的內(nèi)容與消息本身一致,證明消息是A回復(fù)的。

在這個過程中,只有2次傳遞過程,第一次是A傳遞加簽的消息和消息本身給B,第二次是B獲取A的公鑰,即使都被敵方截獲,也沒有危險性,因為只有A的私鑰才能對消息進行簽名,即使知道了消息內(nèi)容,也無法偽造帶簽名的回復(fù)給B,防止了消息內(nèi)容的篡改。

但是,綜合兩個場景你會發(fā)現(xiàn),第一個場景雖然被截獲的消息沒有泄露,但是可以利用截獲的公鑰,將假指令進行加密,然后傳遞給A。第二個場景雖然截獲的消息不能被篡改,但是消息的內(nèi)容可以利用公鑰驗簽來獲得,并不能防止泄露。所以在實際應(yīng)用中,要根據(jù)情況使用,也可以同時使用加密和簽名,比如A和B都有一套自己的公鑰和私鑰,當(dāng)A要給B發(fā)送消息時,先用B的公鑰對消息加密,再對加密的消息使用A的私鑰加簽名,達到既不泄露也不被篡改,更能保證消息的安全性。

總結(jié):公鑰加密、私鑰解密、私鑰簽名、公鑰驗簽。

三、RSA加密、簽名的方法,代碼例子如下:

3.1 首先引入commons-codec

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.14</version>
</dependency>

3.2 新建密鑰對對象

/**
 * @author: huangyibo
 * @Date: 2022/4/29 18:47
 * @Description: 非對稱加密 密鑰對對象
 */

public class RsaKeyPair {

    private String publicKey;

    private String privateKey;

    public RsaKeyPair(String publicKey, String privateKey) {
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }

    public String getPublicKey() {
        return publicKey;
    }

    public String getPrivateKey() {
        return privateKey;
    }
}

3.3 構(gòu)建RSA工具類

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author: huangyibo
 * @Date: 2022/5/6 15:52
 * @Description:
 */

public class RSAUtil {

    /**
     * RSA編碼
     */
    public static final String ALGORITHM = "RSA";

    public static final String SIGN_ALGORITHM = "SHA512withRSA";

    /**
     * 默認種子, 構(gòu)建RSA密鑰對, 生成的密鑰對不變
     */
    private static final String DEFAULT_SEED = "0f22507a10bbddd07d8a3082122966e3";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 構(gòu)建RSA密鑰對
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
        // 初始化隨機產(chǎn)生器
        /*SecureRandom secureRandom = new SecureRandom();
        //加入默認種子, 生成的密鑰對不變
        secureRandom.setSeed(DEFAULT_SEED.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);*/

        // 不加入默認種子, 每次生成的密鑰對會變化
        keyPairGenerator.initialize(1024, new SecureRandom());
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
        String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
        return new RsaKeyPair(publicKeyString, privateKeyString);
    }


    /**
     * RSA 公鑰加密
     * @param data              加密字符串
     * @param publicKeyText     公鑰
     * @return                  密文
     * @throws Exception        加密過程中的異常信息
     */
    public static String encryptByPublicKey(String data, String publicKeyText) throws Exception {
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        int inputLen = data.getBytes().length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        // 對數(shù)據(jù)分段加密
        while (inputLen - offset > 0) {
            if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        // 獲取加密內(nèi)容使用base64進行編碼,并以UTF-8為標(biāo)準(zhǔn)轉(zhuǎn)化成字符串
        // 加密后的字符串
        return Base64.encodeBase64String(encryptedData);
    }


    /**
     * RSA 私鑰解密
     * @param data              加密字符串
     * @param privateKeyText    私鑰
     * @return                  明文
     * @throws Exception        解密過程中的異常信息
     */
    public static String decryptByPrivateKey(String data, String privateKeyText) throws Exception {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] dataBytes = Base64.decodeBase64(data);
        int inputLen = dataBytes.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        // 對數(shù)據(jù)分段解密
        while (inputLen - offset > 0) {
            if (inputLen - offset > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        // 解密后的內(nèi)容
        return new String(decryptedData, StandardCharsets.UTF_8);
    }

    /**
     * 私鑰加密
     *
     * @param data              加密數(shù)據(jù)
     * @param privateKeyText    私鑰
     * @return                  密文
     * @throws Exception        加密過程中的異常信息
     */
    public static String encryptByPrivateKey(String data, String privateKeyText) throws Exception {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        int inputLen = data.getBytes().length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        // 對數(shù)據(jù)分段加密
        while (inputLen - offset > 0) {
            if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        // 獲取加密內(nèi)容使用base64進行編碼,并以UTF-8為標(biāo)準(zhǔn)轉(zhuǎn)化成字符串
        // 加密后的字符串
        return Base64.encodeBase64String(encryptedData);
    }

    /**
     * 公鑰解密
     *
     * @param data          加密字符串
     * @param publicKeyText 公鑰
     * @return              明文
     * @throws Exception    解密過程中的異常信息
     */
    public static String decryptByPublicKey(String data, String publicKeyText) throws Exception {
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        byte[] dataBytes = Base64.decodeBase64(data);
        int inputLen = dataBytes.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        // 對數(shù)據(jù)分段解密
        while (inputLen - offset > 0) {
            if (inputLen - offset > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        // 解密后的內(nèi)容
        return new String(decryptedData, StandardCharsets.UTF_8);
    }


    /**
     * 簽名
     *
     * @param data       待簽名數(shù)據(jù)
     * @param privateKey 私鑰
     * @return 簽名
     */
    public static String sign(String data, String privateKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PrivateKey key = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
        Signature signature = Signature.getInstance(SIGN_ALGORITHM);
        signature.initSign(key);
        signature.update(data.getBytes());
        return new String(Base64.encodeBase64(signature.sign()));
    }

    /**
     * 驗簽
     *
     * @param srcData 原始字符串
     * @param publicKey 公鑰
     * @param sign    簽名
     * @return 是否驗簽通過
     */
    public static boolean verify(String srcData, String publicKey, String sign) throws Exception {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PublicKey key = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGN_ALGORITHM);
        signature.initVerify(key);
        signature.update(srcData.getBytes());
        return signature.verify(Base64.decodeBase64(sign.getBytes()));
    }

    public static void main(String[] args) {
        try {
            // 生成密鑰對
            RsaKeyPair rsaKeyPair = generateKeyPair();
            System.out.println("私鑰:" + rsaKeyPair.getPrivateKey());
            System.out.println("公鑰:" + rsaKeyPair.getPublicKey());

            // RSA 公鑰加密
            String data = "待加密的文字內(nèi)容";
            String encryptPublicKeyData = encryptByPublicKey(data, rsaKeyPair.getPublicKey());
            System.out.println("公鑰加密后內(nèi)容:" + encryptPublicKeyData);

            // RSA 私鑰解密
            String decryptPrivateKeyData = decryptByPrivateKey(encryptPublicKeyData, rsaKeyPair.getPrivateKey());
            System.out.println("私鑰解密后內(nèi)容:" + decryptPrivateKeyData);

            // RSA 私鑰加密
            String data1 = "待加密的文字內(nèi)容";
            String encryptPrivateKeyData = encryptByPrivateKey(data1, rsaKeyPair.getPrivateKey());
            System.out.println("私鑰加密后內(nèi)容:" + encryptPrivateKeyData);
            // RSA 公鑰解密
            String decryptPublicKeyData = decryptByPublicKey(encryptPrivateKeyData, rsaKeyPair.getPublicKey());
            System.out.println("公鑰解密后內(nèi)容:" + decryptPublicKeyData);

            // RSA簽名
            String sign = sign(data, rsaKeyPair.getPrivateKey());
            // RSA驗簽
            boolean result = verify(data, rsaKeyPair.getPublicKey(), sign);
            System.out.println("驗簽結(jié)果:" + result);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.print("加解密異常");
        }
    }
}

PS:RSA加密對明文的長度有所限制,規(guī)定需加密的明文最大長度=密鑰長度-11(單位是字節(jié),即byte),所以在加密和解密的過程中需要分塊進行。而密鑰默認是1024位,即1024位/8位-11=128-11=117字節(jié)。所以默認加密前的明文最大長度117字節(jié),解密密文最大長度為128字。那么為啥兩者相差11字節(jié)呢?是因為RSA加密使用到了填充模式(padding),即內(nèi)容不足117字節(jié)時會自動填滿,用到填充模式自然會占用一定的字節(jié),而且這部分字節(jié)也是參與加密的。

密鑰長度的設(shè)置就是上面例子的keyPairGenerator.initialize(1024, new SecureRandom());??勺孕姓{(diào)整,當(dāng)然非對稱加密隨著密鑰變長,安全性上升的同時性能也會有所下降。

參考:
https://www.cnblogs.com/pcheng/p/9629621.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容