jjwt icon indicating copy to clipboard operation
jjwt copied to clipboard

Using private/public key example

Open cope opened this issue 9 years ago • 13 comments
trafficstars

Hi, can you please provide an example of how to sign/verify using an existing private/public key pair?

I got it working in node with node-jsonwebtoken, like this:

var key = fs.readFileSync('private.key');
var pem = fs.readFileSync('public.pem');

var header = {...};
var payload = {...};

header.algorithm = "RS256";
var message = jsonwebtoken.sign(payload, key, header);
var decoded = jsonwebtoken.verify(message, pem, {algorithm: "RS256"});

And it works just fine.

I would like to do the same in Java.

cope avatar Jun 10 '16 11:06 cope

Here's what I did during testing using a key pair with the library: Create a RSA keypair in a java keystore: keytool -alias jwtkey -keyalg RSA -dname "CN=Server,OU=Unit,O=Organization,L=City,S=State,C=US" -keypass keypassword -keystore server.jks -storepass jkspassword Java code:

public void test() throws Exception {
        ClassPathResource resource = new ClassPathResource("keystore.jks");
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(resource.getInputStream(), "jkspassword".toCharArray());

        Key key = keystore.getKey("jwtkey", "keypassword".toCharArray());
        Certificate cert = keystore.getCertificate("jwtkey");
        PublicKey publicKey = cert.getPublicKey();
        Map<String, Object> claims = new HashMap<>();
                claims.put("user", "cope");
        Calendar expires = Calendar.getInstance();
        expires.roll(Calendar.HOUR, 2);
        String s = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .setExpiration(expires.getTime())
                .signWith(SignatureAlgorithm.RS256, key)
                .compact();
        System.out.println(s);
        Jwts.parser().setSigningKey(publicKey).require("user", "cope").parse(s);

If you need to get the public key, you can do the following:

keytool -export -keystore server.jks -alias jwtkey -file jwtkey.cer
openssl x509 -inform der -in jwtkey.cer -pubkey -not

The openssl command will output the public key.

Hope this helps.

csmithmtb avatar Jun 21 '16 14:06 csmithmtb

Java doesn't support .pem files natively. You'll need to convert from .pem to a format that Java understands. For example:

http://stackoverflow.com/questions/11787571/how-to-read-pem-file-to-get-private-and-public-key

lhazlewood avatar Jun 21 '16 16:06 lhazlewood

@csmithmtb your keystore command is missing the -genkeypair param (at least for me on Java 8)

madmuffin1 avatar Jul 05 '16 10:07 madmuffin1

Can anyone provide another more simple example, please? I'm a very new to Java and i just spend 4 hours to find out how to work with this lib.

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class Application {
    public static void main(String[] args) {
        String privateKey = "privateKey";
        
        String publicKey = "publicKey";

         String compactedJWT =  Jwts.builder().setSubject("syle")
                 .setIssuer("localhost")
                 .signWith(SignatureAlgorithm.RS256, <how about this?>)
                 .compact();
         System.out.println(compactedJWT);
    }
}

I don't know how to create a Key object if i already have a private and public key pair in two text file?

lekhasy avatar Mar 10 '17 08:03 lekhasy

@lekhasy your question is a general JVM question (about key pair files) and not related to JJWT specifically. Did you read my comment above about converting .pem files on the JVM?

http://stackoverflow.com/questions/11787571/how-to-read-pem-file-to-get-private-and-public-key

HTH!

lhazlewood avatar Mar 10 '17 18:03 lhazlewood

Ok, is there anyway to just verify a key using just a public key (perhaps pulled from public rest end point), without going through the pain of a key store?

demaniak avatar Mar 24 '17 12:03 demaniak

@cope here is what I got working: First, the private/public key pair is generated: openssl genrsa -out jwt.pem 2048 openssl rsa -in jwt.pem

that should produce the base64 encoded private key.

For the Base64 encoded public key (easy to use with Spring Boot/Cloud oauth2 and jwt libs): openssl rsa -in jwt.pem -pubout

BUT, to later "manually" read this public key, you need it in der format: openssl rsa -in jwt.pem -pubout -outform DER -out public.der

Ok, so now you have that public.der file, you can do this:

//I'm using spring boot, here, so you might want to obtain the InputStream in some other way.
PublicKey getPublicKey() {
            org.springframework.core.io.Resource pubKeyRes = new ClassPathResource("/public.der");
            java.security.PublicKey pubKey = null;
            try (InputStream in = pubKeyRes.getInputStream()) {

                java.security.spec.X509EncodedKeySpec spec = new X509EncodedKeySpec(IOUtils.toByteArray(in));
                java.security.KeyFactory kf = KeyFactory.getInstance("RSA");
                pubKey = kf.generatePublic(spec);

            } catch (Exception ioE) {
                log.error("FAILED TO LOAD PUBLIC KEY. WE WILL NOT BE ABLE TO VERIFY JWTs.THINGS WILL BREAK", ioE);                
            }
}

io.jsonwebtoken.Jwt<?, ?> jwt = io.jsonwebtoken.Jwts.parser().setSigningKey(getPublicKey()).parse(auth);
log.debug("Parsed JWT: {}", jwt);

Refs:

  • http://stackoverflow.com/questions/11410770/load-rsa-public-key-from-file
  • https://github.com/jwtk/jjwt
  • http://www.baeldung.com/spring-security-oauth-jwt
  • https://beku8.wordpress.com/2015/03/31/configuring-spring-oauth2-with-jwt-asymmetric-rsa-keypair/

demaniak avatar Mar 24 '17 12:03 demaniak

You should be able to do so. Generate the key pair into a keystore. The OAuth2 server app uses the keystore to sign the JWT. Export the public key to a file from the keystore to a PEM file(?). You can copy & paste the contents in the application.yml file in your client apps as 'security.oauth2.resource.jwt.key-value'.

security: oauth2: resource: jwt: key-value: | -----BEGIN PUBLIC KEY----- snip key -----END PUBLIC KEY-----

csmithmtb avatar Mar 24 '17 13:03 csmithmtb

Just a follow up: This is currently outside the scope of JJWT since it s a general Java question (of how to convert a .pem file (or contents) to java Key instance, which isn't a JWT-specific concept at all. That is, this is a general Java question, not a JWT question.

That said, it would be nice if JJWT had a helper function that could do this. However, meeting JWT RFC compliance is our highest priority, and pem-to-javaKey work isn't high on our list right now. I'll tag this issue as enhancement as well as help wanted (in case anyone wants to implement this), but odds are high we won't get to it until after 1.0 is out.

If anyone does want to take a crack at this so it can be used by JJWT users, please open a discussion here first to discuss where the code should live and how it should be decoupled before you spend a lot of work on a PR only to find it rejected due to organization or design incompatibilities with our current work.

lhazlewood avatar Mar 09 '19 19:03 lhazlewood

I had same requirement, I have solved using Auth0's utils, documenting it for others,

  1. https://github.com/auth0/java-jwt/blob/master/lib/src/test/java/com/auth0/jwt/PemUtils.java copy this file to your repo

  2. Include boucny castle as dependency in your project or you can copy paste the required dependency implementation 'org.bouncycastle:bcprov-jdk15on:1.60'

  3. Use readPublicKeyFromFile and/or readPrivateKeyFromFile from PemUtils

PrivateKey privateKey = PemUtils.readPrivateKeyFromFile("private.pem", "EC"); PublicKey publicKey = PemUtils.readPublicKeyFromFile("public.pem", "EC");

riyaz avatar May 30 '19 06:05 riyaz

Using an external library (bouncycastle) or another class is not really necessary at all to read a PEM file. It's like 2 lines of Java code:

import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
...
// First, get your PEM-encoded DER certificate into a String, like this:
String certChart = "-----BEGIN CERTIFICATE-----\n.....";

// Now parse it using CertificateFactory:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(new java.io.ByteArrayInputStream(certChars.getBytes(StandardCharsets.US_ASCII)));

// Now verify:
Jwts.parserBuilder()
    .setSigningKey(cert.getPublicKey())
   ...

ChristopherSchultz avatar May 04 '21 18:05 ChristopherSchultz

Full example for generating, saving, loading and using a public/private key pair:

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;

import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class JwtExample {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.ES512);
        String encodedPublicKeyBase64 = Encoders.BASE64.encode(keyPair.getPublic().getEncoded());
        String encodedPrivateKeyBase64 = Encoders.BASE64.encode(keyPair.getPrivate().getEncoded());

        byte[] encodedPublicKeyBytes = Decoders.BASE64.decode(encodedPublicKeyBase64);
        byte[] encodedPrivateKeyBytes = Decoders.BASE64.decode(encodedPrivateKeyBase64);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedPublicKeyBytes));
        PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKeyBytes));

        String jwt = Jwts.builder().setIssuer("example").claim("foo", "bar").signWith(privateKey).compact();
        JwtParser parser = Jwts.parserBuilder().setSigningKey(publicKey).build();
        Jws<Claims> jws = parser.parseClaimsJws(jwt);
        System.out.println(jws); // header={alg=ES512},body={iss=example, foo=bar},signature=AKoAps_bw...
    }
}

Yanson avatar Dec 30 '21 14:12 Yanson

PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKeyBytes));

thank you. it's helpful

wz2cool avatar Jan 20 '22 15:01 wz2cool

Using a copy/paste Private key PEM-format and Kotlin/Android:

        val rawPrivateKey = "-----BEGIN PRIVATE KEY-----\n" +
                ".........\n" +
                "-----END PRIVATE KEY-----"

        val formattedPrivateKey = rawPrivateKey.replace("\n","")
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replace("-----END PRIVATE KEY-----", "")

        val pkcs8EncodedKey = Base64.decode(formattedPrivateKey, Base64.DEFAULT)
        val factory = KeyFactory.getInstance("RSA")
        val pk = factory.generatePrivate(PKCS8EncodedKeySpec(pkcs8EncodedKey))

        val jws = Jwts.builder()
            .signWith(pk)
            .compact()

jnfran92 avatar Nov 21 '22 22:11 jnfran92