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

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;
    }

需添加依赖,

		
			dingding
			dingding
			2.8
			system
			${project.basedir}/chilkat.jar
		

下载地址: 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_第1张图片

然后就可以在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_第2张图片

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 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 header;
        Map body;
    }
}


        
			io.jsonwebtoken
			jjwt-api
			0.11.2
		
		
			io.jsonwebtoken
			jjwt-impl
			0.11.2
			runtime
		
		
			io.jsonwebtoken
			jjwt-jackson 
			0.11.2
			runtime
		
        
			commons-codec
			commons-codec
			1.10
		
		
		    org.apache.commons
		    commons-lang3
		    3.4
		
		
			org.projectlombok
			lombok
			provided
			1.16.20
		
		
			commons-io
			commons-io
			2.4
		

 

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工具

你可能感兴趣的:(JWT,KONG,jwt)