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
然后就可以在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如下
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工具