欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JWT RS256加解密、JWK获取PublicKey和PrivateKey、从已存在公私钥加解密JWT

程序员文章站 2022-05-04 08:56:05
JWT的简介就没什么必要了,https://jwt.io/官网在这, 直接重点, 在alg=RS256时, 怎么从现有的JWK中获取到公私钥?拿到公私钥后如何加密解密? 背景是在使用kong网关时, 使用jwt插件遇到的问题.https://mkjwk.org/ jwk在线生成https://jwt.io/ jwt官网http://jwt.calebb.net/ jwt反解背景, 从其他网关切换到kong后, 有关jwt的配置需要从现有的jwk配置获取, jwk的形式如下......

JWT的简介就没什么必要了, https://jwt.io/ 官网在这, 直接重点, 在alg=RS256时, 怎么从现有的JWK中获取到公私钥?拿到公私钥后如何加密解密? 背景是在使用kong网关时, 使用jwt插件遇到的问题.

https://mkjwk.org/  jwk在线生成
https://jwt.io/          jwt官网
http://jwt.calebb.net/   jwt反解

背景, 从其他网关切换到kong后, 有关jwt的配置需要从现有的jwk配置获取, jwk的形式如下, 可从https://mkjwk.org/ 生成, RSA tab页

{
    "kty": "RSA",
    "e": "AQAB",
    "use": "sig",
    "kid": "c24a15f4-5b4e-4749-8e1b-9a11221fd31d",
    "alg": "RS256",
    "n": "uUvDQSrvTJIfPsDhNo-6C_i2ZLhsU3T3ZQDqrCMSdkcUOiu0oI28NCkicRIKeV4AZaar9vVk_uhMv4KLKYV441HX-OqHgqVqBPxtWHuZFkHGODg90VFGTPAxG90mkJsz7CcsvujTnPQeTVzYJ5mFga-VH7ZwSUiu5byQJUJeGmvfl3eVt8rc29SSbCHV4cDDqMwJIYMA_Quhppw_LkqGJ9Mz7gh7kw5FxA9IJli13dAE5rx9nr8J5-iXBwM8yAADSDd45PHKkKYi_IYfuAvG1vXwJtjsExOgyVEugv4i7D_gM6Ch2gRrpgxNiP7QnzRDZtmDq37O0kTzppWc9zVX3w"
}

jwk只是存储公私钥的一个形式, 可以从上面的key中获取到publicKey, demo如下

static String getPublicKeyFromJwk(String value) throws Exception {
        PublicKeyJwk publicKeyJwk = new ObjectMapper().readValue(value, PublicKeyJwk.class);
        CkJsonObject json = new CkJsonObject();
        json.UpdateString("kty",publicKeyJwk.getKty());
        json.UpdateString("n",publicKeyJwk.getN());
        json.UpdateString("e",publicKeyJwk.getE());
        json.UpdateString("kid", publicKeyJwk.getKid());
        json.put_EmitCompact(false);

        String jwkStr = json.emit();
        CkPublicKey pubKey = new CkPublicKey();
        boolean success = pubKey.LoadFromString(jwkStr);
        if (!success) {
            System.out.println(pubKey.lastErrorText());
            throw new Exception(pubKey.lastErrorText());
        }
        boolean bPreferPkcs1 = false;
        String pem = pubKey.getPem(bPreferPkcs1);
        System.out.println(pem);

        return pem;
    }
    @Data@JsonIgnoreProperties(ignoreUnknown = true)
    private static class PublicKeyJwk {
        String kty;
        String e;
        String kid;
        String n;
    }

需添加依赖,

		<dependency>
			<groupId>dingding</groupId>
			<artifactId>dingding</artifactId>
			<version>2.8</version>
			<scope>system</scope>
			<systemPath>${project.basedir}/chilkat.jar</systemPath>
		</dependency>

下载地址: http://www.chilkatsoft.com/java.asp,下载文件中有个lib库, 需要启动时指定java.library.path, Idea在run Configuration中制定vm Options, 如下: 

-Djava.library.path=xxx

类库也提供了从jwk中获取私钥的功能, 这时的jwk

{
    "p": "7L7sjNqx5mqIg9fbEiiv06XNyZDJjrHXAsrypOq4n5-m7qP1V0M8J4hBvHkXuAcccBA0d_5tKJYVKhRWzyS3IcAODKVSo_eZRp0fHAhkFBL4g3FrNpn9BHF67W12d6yRqITeP231FQ37751P0xEdIq9B-HGe5TufncAAsAwbKC0",
    "kty": "RSA",
    "q": "yF2lio448krpFzZlEtEvV_lU57dY56S_BVsWXEzb4F_ufjQPyv7fWW2UK8gRDVaPwHWCWoiKTKy_0UU_xPNfTTArxdADCGVdot9rMVbo7cVXGVm7IBwWzaSQPYVxnVdp8JdJzIbjzpg0wizxcCcn3KfXXqaP1-IgxlT3-6fTW7s",
    "d": "qzC34AlOtKt7enqwl7wJ4u2RdVR9oE08E3DZXte4QtZAdc3TP1IzQu2OCHDmhGK4czGdRrhI6sirv3NYJrBNk5cVtb7YG3e_j4O3cjwen1V9UIuFcVFpZcOzW07iRk9dlRxMVsS8XRGcvVS9zzgjBEG3wGjJLKueClo_wmyijDzxqkFEhJKqwtxENdRBoLnnwWVW6FotPsT_YK6oXLqmzZ7lxAysBAGGmhCf7BpyU7DGkiyueXXGewy2k28EGiHvD6wkjJDrxkxpDzkUiTEWxz3bXaiMHjOoQDC7Me9uQdxy4dhihCvHpja6mWp-rU74zqR5ilA7S0_9ZEZfEAzr4Q",
    "e": "AQAB",
    "use": "sig",
    "kid": "c24a15f4-5b4e-4749-8e1b-9a11221fd31d",
    "qi": "e9xK1naCfXL7YGnkRN8oqK380o57484ZNTL6k-cPAJov4C-H63nmWNWMhlLHHdEWcEpQyHfHa3gT2XWyGHkKkDSwXNjYm6kmUI-smH4nrKYgY_oa2aXNulnjxd9eQH8YsyhybCNc40uWlBWqwPAQYEeDkxpTre4OUzCNJ2tOdSs",
    "dp": "hH3xGn8F0qLKVabG5mm4xOTkvyp1cpNadiioFN17h3Gs1Z8Sncx17NXXnCfUu1vXcWvQQVs1MeKUY6FQV8r_Zjb6Zd9b2YGm2RrznxefEpDvXXhq_Pq-2-66UgfRpfYA6mO5kZvy7d6OoTHTy5anTJLyg5zqxPVSRdF_UQblZ90",
    "alg": "RS256",
    "dq": "PuEcrXHarzcRFWbNq20YdXxax-lDLlcGV5DxYIACVNTmTJbcCfGYeEEqSd8cctoifNyjzvOgq1VfUTZxP8a8tsWSRx7zhLQDAbUpt681pEDVB7CgSABoq5qkZZo2QJGJPqbL0zLV1STxEar3DiJLoTTPIvYUmERv0q4hsMlHTDc",
    "n": "uUvDQSrvTJIfPsDhNo-6C_i2ZLhsU3T3ZQDqrCMSdkcUOiu0oI28NCkicRIKeV4AZaar9vVk_uhMv4KLKYV441HX-OqHgqVqBPxtWHuZFkHGODg90VFGTPAxG90mkJsz7CcsvujTnPQeTVzYJ5mFga-VH7ZwSUiu5byQJUJeGmvfl3eVt8rc29SSbCHV4cDDqMwJIYMA_Quhppw_LkqGJ9Mz7gh7kw5FxA9IJli13dAE5rx9nr8J5-iXBwM8yAADSDd45PHKkKYi_IYfuAvG1vXwJtjsExOgyVEugv4i7D_gM6Ch2gRrpgxNiP7QnzRDZtmDq37O0kTzppWc9zVX3w"
}

java demo

static String getPrivateKeyFromJwk(String value) throws Exception{
        KeyPairJwk jwk = new ObjectMapper().readValue(value, KeyPairJwk.class);
        CkJsonObject json = new CkJsonObject();
        json.UpdateString("kty",jwk.getKty());
        json.UpdateString("n",jwk.getN());
        json.UpdateString("e",jwk.getE());
        json.UpdateString("d",jwk.getD());
        json.UpdateString("p",jwk.getP());
        json.UpdateString("q",jwk.getQ());
        json.UpdateString("dp",jwk.getDp());
        json.UpdateString("dq",jwk.getDq());
        json.UpdateString("qi",jwk.getQi());
        json.put_EmitCompact(false);

        String jwkStr = json.emit();

        CkPrivateKey privKey = new CkPrivateKey();
        boolean success = privKey.LoadJwk(jwkStr);
        if (!success) {
            System.out.println("load error: \n" + privKey.lastErrorText());
            throw new Exception(privKey.lastErrorText());
        }
        String secret = privKey.getRsaPem();
        System.out.println(secret);
        return secret;
    }

现在就已经拿到公私钥了, 接下来可以用在kong上尝试配置一下能否加解密成功, 不想手动写代码生成Token可以用在线工具: https://jwt.io/ 选择RS256, publickey和privatekey填上刚才生成的, 如果一切正常, 左侧会出现token, 左侧下部会提示Signature Verified

JWT RS256加解密、JWK获取PublicKey和PrivateKey、从已存在公私钥加解密JWT

然后就可以在kong上配置公钥, 配置Consumer, 具体kong的配置先不说了

kong的校验结束后, 如果我们想在java端加解密, 还需要注意密钥的填充格式的问题,  现在获取出来的密钥是pkcs1的, 如果希望用下面的方式获取PrivateKey, 在根据这个PrivateKey加密得到jwt, 那么需要转化为pkcs8填充方式

    private static PrivateKey getPrivateKey(String privateKey) throws Exception {
        privateKey = privateKey.replaceAll("-*BEGIN.*KEY-*", "")
                .replaceAll("-*END.*KEY-*", "")
                .replaceAll("\\s+","");

        byte[] encodedKey = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);

        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privKey = kf.generatePrivate(keySpec);
        return privKey;
    }
-----BEGIN RSA PRIVATE KEY-----   pkcs1
-----BEGIN PRIVATE KEY-----       pkcs8

如果不转化会报错

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence

从pkcs1转为pkcs8的命令为

openssl pkcs8 -topk8 -inform PEM -in ${in_path} -outform pem -nocrypt -out ${out_path}

之后就可以用pkcs8格式的密钥生成token, 用publicKey解密了. 全部demo如下, 在resources/jwk下放这几个文件, PublicKey是从生成JWK的网站上拷贝的 Public Key 部分的数据, PublicAndPrivateKeyPair是拷贝的Public and Private Keypair部分的数据, JwtBody.json如下

JWT RS256加解密、JWK获取PublicKey和PrivateKey、从已存在公私钥加解密JWT

JwtBody.json
{
    "header" : {
        "alg": "RS256",
        "kid": null
    },
    "body" : {
        "exp": 1598424022,
        "sub": "username"
    }
}


import com.chilkatsoft.CkJsonObject;
import com.chilkatsoft.CkPrivateKey;
import com.chilkatsoft.CkPublicKey;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

// vm options -Djava.library.path=/Users/fengzhikui/data/fzknotebook/fzk-custom-project/fzk-encode
public class JwkRs256Generator {
    static {
        try {
            System.loadLibrary("chilkat");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
    static final String JWT_BODY_PATH = "jwk/JwtBody.json";
    static final String PUBLIC_KEY_PATH = "jwk/PublicKey";
    static final String PAIR_KEY_PATH = "jwk/PublicAndPrivateKeypair";

    static final String RESULT_PATH = "/src/main/resources/result/%s-%s/";//相对当前路径的存放路径

    static String kid = null;
    static String path = null;
    static String publicKeyPath = null;
    static String privatePkcs1Path = null;
    static String privatePkcs8Path = null;
    static String tokenPath = null;

    public static void main(String[] args) throws Exception {
        initPath();
        String publicKeyStr = FileUtil.read(PUBLIC_KEY_PATH);
        String publicKeyFromJwk = getPublicKeyFromJwk(publicKeyStr);

        String privateKeyStr = FileUtil.read(PAIR_KEY_PATH);
        String privateKeyFromJwk = getPrivateKeyFromJwk(privateKeyStr);

        FileUtil.write(publicKeyFromJwk, publicKeyPath);
        FileUtil.write(privateKeyFromJwk, privatePkcs1Path);
        pkcs1ToPkcs8();

        PrivateKey privateKey = getPrivateKeyFromExist(privatePkcs1Path);
        String token = generateToken(privateKey);
        FileUtil.write(token, tokenPath);

        PublicKey publicKey = getPublicKeyFromExist(publicKeyPath);
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
        System.out.println(claimsJws);
        FileUtil.write("\n" + claimsJws.toString(), tokenPath, true);
    }

    public static String generateToken(PrivateKey privateKey) throws Exception {
        String jwtBody = FileUtil.read(JWT_BODY_PATH);
        JwtContent jwt = new ObjectMapper().readValue(jwtBody, JwtContent.class);
        jwt.getHeader().put("kid", kid);

        String token = Jwts.builder()
                .setHeader(jwt.getHeader())
                .setClaims(jwt.getBody())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
        System.out.println(token);
        return token;
    }

    private static PrivateKey getPrivateKeyFromExist(String path) throws Exception {
        return getPrivateKey(FileUtil.read(path));
    }

    private static PrivateKey getPrivateKey(String privateKey) throws Exception {
        privateKey = privateKey.replaceAll("-*BEGIN.*KEY-*", "")
                .replaceAll("-*END.*KEY-*", "")
                .replaceAll("\\s+","");

        byte[] encodedKey = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);

        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privKey = kf.generatePrivate(keySpec);
        return privKey;
    }


    private static PublicKey getPublicKeyFromExist(String path) throws Exception {
        String s = FileUtil.read(path);
        return getPublicKey(s);
    }

    private static PublicKey getPublicKey(String publicKeyBase64) throws Exception {
        String pem = publicKeyBase64
                .replaceAll("-*BEGIN.*KEY-*", "")
                .replaceAll("-*END.*KEY-*", "")
                .replaceAll("\\s+","");
        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(pem));
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
        return publicKey;
    }

    static void pkcs1ToPkcs8() throws Exception {
        String cmd = "openssl pkcs8 -topk8 -inform PEM -in %s -outform pem -nocrypt -out %s";
        cmd = String.format(cmd, privatePkcs1Path, privatePkcs8Path);
        BufferedReader br = null;
        try {
            Process p = Runtime.getRuntime().exec(cmd);
            br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            p.waitFor();
        } finally {
            if (br != null) { br.close(); }
        }
    }

    static void initPath() throws Exception{
        String absolutePath = FileUtil.getAbsolutePath(PUBLIC_KEY_PATH);
        String publicKeyStr = FileUtil.read(PUBLIC_KEY_PATH);
        PublicKeyJwk publicKeyJwk = new ObjectMapper().readValue(publicKeyStr, PublicKeyJwk.class);
        path = String.format(RESULT_PATH,
                publicKeyJwk.getKid() == null ? "" : publicKeyJwk.getKid().substring(0, 8),
                new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
        path = new File("").getAbsolutePath() + path;
        kid = publicKeyJwk.getKid();
        publicKeyPath = path + "public-key.pem";
        privatePkcs1Path = path + "private-key-pkcs1.pem";
        privatePkcs8Path = path + "private-key-pkcs8.pem";
        tokenPath = path + "token.txt";
    }


    static String getPublicKeyFromJwk(String value) throws Exception {
        PublicKeyJwk publicKeyJwk = new ObjectMapper().readValue(value, PublicKeyJwk.class);
        CkJsonObject json = new CkJsonObject();
        json.UpdateString("kty",publicKeyJwk.getKty());
        json.UpdateString("n",publicKeyJwk.getN());
        json.UpdateString("e",publicKeyJwk.getE());
        json.UpdateString("kid", publicKeyJwk.getKid());
        json.put_EmitCompact(false);

        String jwkStr = json.emit();
        CkPublicKey pubKey = new CkPublicKey();
        boolean success = pubKey.LoadFromString(jwkStr);
        if (!success) {
            System.out.println(pubKey.lastErrorText());
            throw new Exception(pubKey.lastErrorText());
        }
        boolean bPreferPkcs1 = false;
        String pem = pubKey.getPem(bPreferPkcs1);
        System.out.println(pem);

        return pem;
    }

    static String getPrivateKeyFromJwk(String value) throws Exception{
        KeyPairJwk jwk = new ObjectMapper().readValue(value, KeyPairJwk.class);
        CkJsonObject json = new CkJsonObject();
        json.UpdateString("kty",jwk.getKty());
        json.UpdateString("n",jwk.getN());
        json.UpdateString("e",jwk.getE());
        json.UpdateString("d",jwk.getD());
        json.UpdateString("p",jwk.getP());
        json.UpdateString("q",jwk.getQ());
        json.UpdateString("dp",jwk.getDp());
        json.UpdateString("dq",jwk.getDq());
        json.UpdateString("qi",jwk.getQi());
        json.put_EmitCompact(false);

        String jwkStr = json.emit();

        CkPrivateKey privKey = new CkPrivateKey();
        boolean success = privKey.LoadJwk(jwkStr);
        if (!success) {
            System.out.println("load error: \n" + privKey.lastErrorText());
            throw new Exception(privKey.lastErrorText());
        }
        String secret = privKey.getRsaPem();
        System.out.println(secret);
        return secret;
    }

    static class FileUtil {
        static String read(String filename) throws Exception {
            if (filename.startsWith("/")) {
                File file = new File(filename);
                return IOUtils.toString(new FileInputStream(file));
            } else {
                URL url = JwkRs256Generator.class.getClassLoader().getResource(filename);
                File file = new File(url.getFile());
                return IOUtils.toString(new FileInputStream(file));
            }
        }
        static void write(String value, String filename) throws Exception {
            File file = new File(filename);
            FileUtils.touch(file);
            IOUtils.write(value, new FileOutputStream(file));
        }
        static void write(String value, String filename, boolean append) throws Exception {
            File file = new File(filename);
            FileUtils.touch(file);
            FileUtils.write(file, value,"UTF-8", append);
        }
        static String getAbsolutePath(String path) {
            ClassLoader classLoader = JwkRs256Generator.class.getClassLoader();
            URL url = classLoader.getResource(path);
            File file = new File(url.getFile());
            return file.getAbsolutePath();
        }
    }

    @Data@JsonIgnoreProperties(ignoreUnknown = true)
    private static class KeyPairJwk {
        String p;
        String kty;
        String q;
        String d;
        String e;
        String kid;
        String qi;
        String dp;
        String dq;
        String n;
    }
    @Data@JsonIgnoreProperties(ignoreUnknown = true)
    private static class PublicKeyJwk {
        String kty;
        String e;
        String kid;
        String n;
    }
    @Data@JsonIgnoreProperties(ignoreUnknown = true)
    private static class JwtContent {
        Map<String, Object> header;
        Map<String, Object> body;
    }
}


        <dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.2</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.2</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
			<version>0.11.2</version>
			<scope>runtime</scope>
		</dependency>
        <dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
			<version>1.10</version>
		</dependency>
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-lang3</artifactId>
		    <version>3.4</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
			<version>1.16.20</version>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.4</version>
		</dependency>

 

https://www.example-code.com/java/publickey_rsa_load_jwk.asp    Load RSA Public Key from JWK Format (JSON Web Key)

https://github.com/jwtk/jjwt                jwt工具

本文地址:https://blog.csdn.net/badboy_fzk/article/details/107650945

相关标签: JWT KONG