Java 實現 ECC 非對稱加密算法加解密和簽名驗簽

一、ECC 橢圓曲線算法簡介

ECC是橢圓曲線算法,其加密算法叫ECIES,簽名算法叫ECDSA。JDK 并不支持 ECC 算法,可以引入 BouncyCastle 庫使用。ECC算法相當耗費資源,如果單純使用CPU進行加密/解密,效率低下。

引入 BouncyCastle 庫

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

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

二、ECC 加解密代碼實例

1.生成 ECC 密鑰

由于 BouncyCastle 庫對 keysize 有指定要求的,可以看 BouncyCastle 庫中的類:KeyPairGeneratorSpi,從中可以看出支持的 keysize 就這么幾個:192、239、256、224、384、521。

注意: 需要使用靜態初始化塊初始化 Provider,常量 EC_ALGORITHM = ”EC“ EC_PROVIDER = “BC”

static{
    try{
        Security.addProvider(new BouncyCastleProvider());
    }catch(Exception e){
        e.printStackTrace();
    }
}
  • 密鑰對對象
/**
 * @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;
    }
}
  • 生成密鑰對
/**
 * 生成密鑰對
 *
 * @param keySize   密鑰長度
 * @return          密鑰對象
 */
public static RsaKeyPair generateEccKeyPair(int keySize) {
    try {
        // 獲取指定算法的密鑰對生成器
        KeyPairGenerator generator = KeyPairGenerator.getInstance(EC_ALGORITHM, EC_PROVIDER);
        // 初始化密鑰對生成器(指定密鑰長度, 使用默認的安全隨機數源)
        generator.initialize(keySize);
        // 隨機生成一對密鑰(包含公鑰和私鑰)
        KeyPair keyPair = generator.generateKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());
        String privateKeyString = Base64.encodeBase64String(privateKey.getEncoded());
        return new RsaKeyPair(publicKeyString, privateKeyString);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

2.ECC 加解密

ECIES_ALGORITHM = “ECIES”
EC_PROVIDER = “BC”

/**
 * ECC 加密
 *
 * @param publicKeyText 公鑰
 * @param data      原文
 * @return 密文
 */
public static String eccEncrypt(String publicKeyText, String data) {
    try {
        X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
        KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
        Cipher cipher = Cipher.getInstance(ECIES_ALGORITHM, EC_PROVIDER);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] result = cipher.doFinal(data.getBytes());
        return Base64.encodeBase64String(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * ECC 解密
 *
 * @param privateKeyText 私鑰
 * @param data          密文
 * @return 原文
 */
public static String eccDecrypt(String privateKeyText, String data) {
    try {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
        KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
        Cipher cipher = Cipher.getInstance(ECIES_ALGORITHM, EC_PROVIDER);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] result = cipher.doFinal(Base64.decodeBase64(data));
        return new String(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

3.測試代碼

public static void main(String[] args) {
    // 測試文本 
    String plain = "12345678";

    // 生成密鑰對
    RsaKeyPair rsaKeyPair = generateEccKeyPair(256);
    System.out.println("ECC公鑰: "+rsaKeyPair.getPublicKey());
    System.out.println("ECC私鑰: "+rsaKeyPair.getPrivateKey());

    // 加解密
    String encrypt = eccEncrypt(rsaKeyPair.getPublicKey(), plain);
    String decrypt = eccDecrypt(rsaKeyPair.getPrivateKey(), encrypt);
    System.err.println(plain.equals(decrypt));
}

三、ECC 簽名驗簽代碼實例

簽名驗簽使用的密鑰同上,簽名算法使用:SHA256withECDSA 即 SIGNATURE = “SHA256withECDSA”。

1. ECC 簽名驗簽

/**
 * 私鑰簽名
 *
 * @param privateKey 私鑰
 * @param data       原文
 * @return 簽名
 */
public static String eccSign(String privateKey, String data) {
    try {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
        KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
        PrivateKey key = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
        Signature signature = Signature.getInstance(SIGNATURE);
        signature.initSign(key);
        signature.update(data.getBytes());
        return new String(Base64.encodeBase64(signature.sign()));
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 公鑰驗簽
 *
 * @param publicKey 公鑰
 * @param srcData   原文
 * @param sign      簽名
 * @return
 */
public static boolean eccVerify(String publicKey, String srcData, String sign) {
    try {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
        KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
        PublicKey key = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE);
        signature.initVerify(key);
        signature.update(srcData.getBytes());
        return signature.verify(Base64.decodeBase64(sign.getBytes()));
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

2. 測試代碼

public static void main(String[] args) {
    // 測試文本
    String plain = "12345678";

    // 生成密鑰對
    RsaKeyPair rsaKeyPair = generateEccKeyPair(256);
    System.out.println("ECC公鑰: "+rsaKeyPair.getPublicKey());
    System.out.println("ECC私鑰: "+rsaKeyPair.getPrivateKey());

    // 簽名驗簽
    String sign = eccSign(rsaKeyPair.getPrivateKey(), plain);
    boolean verify = eccVerify(rsaKeyPair.getPublicKey(), plain, sign);
    System.err.println(verify);
}

完整代碼

import com.yibo.dsa.RsaKeyPair;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author: huangyibo
 * @Date: 2022/4/29 10:31
 * @Description:
 */

public class ECCUtils {

    private static final String EC_ALGORITHM = "EC";

    private static final String EC_PROVIDER = "BC";

    private static final String ECIES_ALGORITHM = "ECIES";

    private static final String SIGNATURE = "SHA256withECDSA";

    static{
        try{
            Security.addProvider(new BouncyCastleProvider());
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 生成密鑰對
     *
     * @param keySize   密鑰長度
     * @return          密鑰對象
     */
    public static RsaKeyPair generateEccKeyPair(int keySize) {
        try {
            // 獲取指定算法的密鑰對生成器
            KeyPairGenerator generator = KeyPairGenerator.getInstance(EC_ALGORITHM, EC_PROVIDER);
            // 初始化密鑰對生成器(指定密鑰長度, 使用默認的安全隨機數源)
            generator.initialize(keySize);
            // 隨機生成一對密鑰(包含公鑰和私鑰)
            KeyPair keyPair = generator.generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());
            String privateKeyString = Base64.encodeBase64String(privateKey.getEncoded());
            return new RsaKeyPair(publicKeyString, privateKeyString);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * ECC 加密
     *
     * @param publicKeyText 公鑰
     * @param data      原文
     * @return 密文
     */
    public static String eccEncrypt(String publicKeyText, String data) {
        try {
            X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
            KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
            Cipher cipher = Cipher.getInstance(ECIES_ALGORITHM, EC_PROVIDER);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] result = cipher.doFinal(data.getBytes());
            return Base64.encodeBase64String(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * ECC 解密
     *
     * @param privateKeyText 私鑰
     * @param data          密文
     * @return 原文
     */
    public static String eccDecrypt(String privateKeyText, String data) {
        try {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
            KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
            Cipher cipher = Cipher.getInstance(ECIES_ALGORITHM, EC_PROVIDER);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] result = cipher.doFinal(Base64.decodeBase64(data));
            return new String(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私鑰簽名
     *
     * @param privateKey 私鑰
     * @param data       原文
     * @return 簽名
     */
    public static String eccSign(String privateKey, String data) {
        try {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
            KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
            PrivateKey key = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
            Signature signature = Signature.getInstance(SIGNATURE);
            signature.initSign(key);
            signature.update(data.getBytes());
            return new String(Base64.encodeBase64(signature.sign()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 公鑰驗簽
     *
     * @param publicKey 公鑰
     * @param srcData   原文
     * @param sign      簽名
     * @return
     */
    public static boolean eccVerify(String publicKey, String srcData, String sign) {
        try {
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
            KeyFactory keyFactory = KeyFactory.getInstance(EC_ALGORITHM);
            PublicKey key = keyFactory.generatePublic(keySpec);
            Signature signature = Signature.getInstance(SIGNATURE);
            signature.initVerify(key);
            signature.update(srcData.getBytes());
            return signature.verify(Base64.decodeBase64(sign.getBytes()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) {
        // 測試文本
        String plain = "12345678";

        // 生成密鑰對
        RsaKeyPair rsaKeyPair = generateEccKeyPair(256);
        System.out.println("ECC公鑰: "+rsaKeyPair.getPublicKey());
        System.out.println("ECC私鑰: "+rsaKeyPair.getPrivateKey());

        // 加解密
        String encrypt = eccEncrypt(rsaKeyPair.getPublicKey(), plain);
        String decrypt = eccDecrypt(rsaKeyPair.getPrivateKey(), encrypt);
        System.err.println(plain.equals(decrypt));

        // 簽名驗簽
        String sign = eccSign(rsaKeyPair.getPrivateKey(), plain);
        boolean verify = eccVerify(rsaKeyPair.getPublicKey(), plain, sign);
        System.err.println(verify);
    }
}

參考:
https://blog.csdn.net/yuanjian0814/article/details/109815473

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容