ECDSA密鑰對生成以及在Token中的應用


1 概述

本文主要講述了如何利用Openssl生成ECDSA密鑰對,並利用Auth0庫進行Token生成及驗證的過程。

2 ECDSA

2.1 簡介

ECCElliptic Curve Cryptography,橢圓曲線加密)是一種基於橢圓曲線數學的公鑰加密算法,而ECDSA是使用ECC對數字簽名算法(DSA)的模擬,總的來說ECC相比起常見的RSA更加安全並且生成密鑰對的過程會更快。本文不會涉及過多原理性的東西,只是作簡單的介紹,想要詳情了解這些算法的可以戳這里

2.2 密鑰對生成

Openssl中生成ECDSA密鑰對的流程如下:

openssl ecparam -genkey -name secp521r1 -out private.pem #生成私鑰
openssl ec -in private.pem -pubout -out public.pem #生成公鑰

參數說明如下:

  • ecparamEC參數設置以及生成命令
  • -genkey:使用特定參數生成EC私鑰
  • -nameec參數,可以使用openssl ecparam -list_curves 查看,這里用的是secp521r1
  • -out:輸出文件名
  • ecEC密鑰處理命令
  • -in:輸入文件
  • -pubout:默認情況下會輸出私鑰,加上該選項會變成輸出公鑰(如果輸入是公鑰的情況下該參數會自動設置)

執行完命令后就成功生成密鑰對了,可以查看一下:

在這里插入圖片描述

密鑰對生成之后就可以准備一下生成Token了。

3 Auth0中的Token應用

3.1 Auth0

Auth0提供了驗證以及授權服務,這里利用官方提供的Java實現去生成Token(這里插一句題外話,Java常用的Token實現還有一個叫JJWT的庫),首先引入包:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.12.0</version>
</dependency>

Gradle

compile group: 'com.auth0', name: 'java-jwt', version: '3.12.0'

引入后來看一下支持的加密算法,如下圖:

在這里插入圖片描述

最簡單的使用HMAC算法生成的Token如下:

System.out.println(JWT.create().withIssuer("issuer").withAudience("content").sign(Algorithm.HMAC512("password")));

當然這不是本文的重點,本文的重點是介紹如何利用ECDSA去生成Token

首先Auth0提供的簽名api如下:

JWT.create().sign(Algorithm)

其中Algorithm可以取值如下:

在這里插入圖片描述

在這里插入圖片描述

想要使用ECDSA算法需要提供一個ECDSAKeyProvider或一個ECPublicKey和一個ECPrivateKey,這里選擇后一種方式實現。

3.2 密鑰對處理

官方並沒有提供如何生成ECPublicKey/ECPrivateKey的方法,甚至連從文件讀取密鑰對的方法都沒有提供,筆者從官方提供的測試代碼中發現了如下方法:

在這里插入圖片描述

其中核心就是讀取密鑰對的兩個方法:

  • readPublicKeyFromFile
  • readPrivateKeyFromFile

import結果可以看到這是一個工具類:

在這里插入圖片描述

但問題是官方該工具類是測試使用的,換句話說不對外暴露的,在IDEA中直接引入會報錯:

在這里插入圖片描述

因此直接找到該工具類的源碼(鏈接可以戳這里,需要引入bouncycastle包,Maven倉庫鏈接可以戳這里

package com.auth0.jwt;

import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class PemUtils {

    private static byte[] parsePEMFile(File pemFile) throws IOException {
        if (!pemFile.isFile() || !pemFile.exists()) {
            throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
        }
        PemReader reader = new PemReader(new FileReader(pemFile));
        PemObject pemObject = reader.readPemObject();
        byte[] content = pemObject.getContent();
        reader.close();
        return content;
    }

    private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) {
        PublicKey publicKey = null;
        try {
            KeyFactory kf = KeyFactory.getInstance(algorithm);
            EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            publicKey = kf.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the public key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the public key");
        }

        return publicKey;
    }
    
    private static PrivateKey getPrivateKey(byte[] keyBytes, String algorithm) {
        PrivateKey privateKey = null;
        try {
            KeyFactory kf = KeyFactory.getInstance(algorithm);
            EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            privateKey = kf.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the private key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the private key");
        }

        return privateKey;
    }

    public static PublicKey readPublicKeyFromFile(String filepath, String algorithm) throws IOException {
        byte[] bytes = PemUtils.parsePEMFile(new File(filepath));
        return PemUtils.getPublicKey(bytes, algorithm);
    }

    public static PrivateKey readPrivateKeyFromFile(String filepath, String algorithm) throws IOException {
        byte[] bytes = PemUtils.parsePEMFile(new File(filepath));
        return PemUtils.getPrivateKey(bytes, algorithm);
    }
}

直接復制該工具類后,將前一步生成的private.pem以及public.pem放置合適位置,通過工具類讀取並生成Token

public class Main {
    public static void main(String[] args) {
        try {
            ECPublicKey publicKey = (ECPublicKey) PemUtils.readPublicKeyFromFile("src/main/resources/public.pem","EC");
            ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile("src/main/resources/private.pem","EC");
            Algorithm algorithm = Algorithm.ECDSA512(publicKey,privateKey);
            String token = JWT.create()
                    .withIssuer("issuer")
                    .sign(algorithm);
            JWTVerifier verifier = JWT.require(algorithm).build();
            verifier.verify(JWT.decode(token));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

但是會報錯說私鑰是null

在這里插入圖片描述

從官方issue中查到了類似的問題

在這里插入圖片描述

回答說是密鑰格式的問題,其中提到的pkcs8私鑰格式轉換命令

在這里插入圖片描述

將私鑰的格式進行轉換:

openssl pkcs8 -topk8 -inform pem -in private.pem -outform pem -nocrypt -out newprivate.pem

參數說明:

  • pkcs8:私鑰格式轉換命令
  • -topk8:讀取私鑰並轉換為PKCS#8格式
  • -inform:指定輸入格式,默認pem,使用該參數並配合-topk8后會生成加密后的PKCS#8格式的私鑰
  • -in:輸入文件
  • -outform:與-inform類似
  • -nocrypt:在這里主要配合-inform+-topk8使用,生成不加密的PrivateKeyInfo而不是加密的PKCS#8 EncryptedPrivateKeyInfo,因為一些軟件(比如某些版本的Java代碼)使用的是不加密格式的私鑰
  • -out:輸出文件

轉換后就可以生成Token了。

3.3 生成Token

最后readPrivateKeyFromFile中的參數修改為新的私鑰即可:

ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile("src/main/resources/newprivate.pem","EC");

4 參考源碼

包含了示例密鑰對以及如何使用(Gradle版的):

5 參考網站

1、知乎-ECC橢圓曲線加密算法:介紹

2、什么是橢圓曲線數字簽名算法(ECDSA)?

3、Elliptic Curve Cryptography: breaking security and a comparison with RSA

4、OpenSSL doc

5、StackExange-https://superuser.com/questions/1103401/generate-an-ecdsa-key-and-csr-with-openssl

6、auth0/java-jwt Issue - ECDSA key version mismatch from openssl pem files #270

7、auth0/java-jwt Github

8、codota-How to useECDSA512methodincom.auth0.jwt.algorithms.Algorithm

如果覺得文章好看,歡迎點贊。

同時歡迎關注微信公眾號:氷泠之路。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM