zoukankan      html  css  js  c++  java
  • 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

    如果觉得文章好看,欢迎点赞。

    同时欢迎关注微信公众号:氷泠之路。

  • 相关阅读:
    shelve模块和xml模块
    time模块,random模块和shutil模块
    包的使用
    目录开发规范
    redis 初步认识四(redis锁,防并发)
    redis 初步认识三(设置登录密码)
    redis 初步认识二(c#调用redis)
    微信小程序 初步认识一(微信运动步数)
    redis 初步认识一(下载安装redis)
    c# 7.0 6.0 新语法
  • 原文地址:https://www.cnblogs.com/6b7b5fc3/p/14218570.html
Copyright © 2011-2022 走看看