Secret Rotation for JWT Tokens
Using some form of secret rotation when using web tokens to encrypt payloads is important to any security strategy. Read on for an example of how to implement this.
Join the DZone community and get the full member experience.
Join For Freewhen you are using json web tokens ( jwts ), or any other token technology that requires you to sign or encrypt payload information, it is important to set an expiration date to the token, so if the token expires, you can either assume that this might be considered a security breach and you refuse any communication using this token, or you can decide to enable the token by updating it with a new expiry date.
but it is also important to use some kind of secret rotation algorithm, so the secret used to sign or encrypt a token is periodically updated, so if the secret is compromised, the tokens leaked by this key is less than it would be otherwise. also, in this way, you are decreasing the probability of a secret being broken.
there are several strategies for implementing this, but in this post, i am going to explain how i implemented secret rotation in one project i developed some years ago to sign jwt tokens with an hmac algorithm.
i am going to show you how to create a jwt token in java.
try {
algorithm algorithm = algorithm.hmac256("secret");
string token = jwt.create()
.withissuer("auth0")
.sign(algorithm);
} catch (unsupportedencodingexception exception){
//utf-8 encoding not supported
} catch (jwtcreationexception exception){
//invalid signing configuration / couldn't convert claims.
}
notice that what you need to do here is to create an algorithm object, set an hmac algorithm, and set a secret that is used to sign and verify the instance.
so what we need is to rotate this algorithm instance every x minutes, so that the probability of breaking the secret, and that the broken secret is still valid, becomes very low.
so how to rotate secrets? well, with a really simple algorithm that everyone (even if you are not a crypto expert) can understand. just using time.
so to generate the secret, you need a string. in the previous example, the was secret. this, of course, is not so secure, so the idea is to compose this secret string by a root (something we called the big bang part) + a shifted time part. in summary, the secret is <bigbang> + <timeinmilliseconds>
the big bang part has no mystery, it is just a static part, for example, my_super_secret.
the interesting part is the time part. suppose you want to renew the secret every second. you only need to do this:
long t = system.currenttimemillis();
system.out.println(t);
system.out.println((t/1000)*1000);
timeunit.milliseconds.sleep(50);
t = system.currenttimemillis();
system.out.println((t/1000)*1000);
i am just putting 0s to the milliseconds part, so if i run this i get something like:
1515091335543
1515091335500
1515091335500
notice that although between the second and third print it has passed 50 milliseconds, the time part is exactly the same. and it will be the same during the same second.
of course, this is an extreme example where the secret is changed every second but the idea is that you remove the part of the time that you want to ignore, and fill it with 0s. for this reason, first, you are dividing the time and then multiplying by the same number.
for example, suppose that you want to rotate the secret every 10 minutes. you just need to divide and multiply for 600000.
there are two problems with this approach that can be fixed, although one of them is not really a big issue.
the first one is that since you are truncating the time, if you want to change the secret every minute and, for example, the first calculation occurs in the middle of a minute, then for just this initial case, the rotation will occur after 30 seconds and not 1 minute. not a big problem and in our project, we did nothing to fix it.
the second one is what's happening with tokens that were signed just before the secret rotation. they are still valid and you need to be able to verify them too, not with the new secret but with the previous one.
to fix this, what we did was to create a valid window, where the previous valid secret was also maintained. so when the system receives a token, it is verified with the current secret. if it passes, then we can do any other checks and work with it, if not, then the token is verified by the previous secret. if it passes, the token is recreated and signed with the new secret, and if not then obviously this token is invalid and must be refused.
to create the algorithm object for our jwt, you only need to do something like:
long currenttime = system.currenttimemillis();
try {
return algorithm.hmac256("my_big_bang" + (currenttime/60000)*60000);
} catch (unsupportedencodingexception e) {
throw new illegalargumentexception(e);
}
what i really like about this solution is:
- it is clean, no need for extra elements on your system.
- no need for triggered threads that are run asynchronously to update the secret.
- it is really performant, you don't need to access an external system.
- testing the service is really easy.
- the process of verifying is responsible for rotating the secret.
- it is really easy to scale, in fact, you don't need to do anything, you can add more and more instances of the same service and all of them will rotate the secret at the same time, and all of them will use the same secret. so, the rotating process is really stateless, you can scale your instances up or down, and all instances will continue to be able to verify tokens signed by other instances.
but, of course, there are some drawbacks:
- you still need to share a part of the secret (the big bang part) to each of the services in a secure way. maybe using kubernetes secrets, vault from hashicorp, or if you are not using microservices, you can just copy a file into a concrete location and when the service is up and running, read the big bang part, and then just remove it.
- if your physical servers are in different time zones, then using this approach might be more problematic. also, you need the servers to be more or less synchronized. since you are storing the previous token and current token, it is not necessary that they are synced in the same second and some seconds delay is still possible without any issue.
so we have seen a really simple way of rotating secrets so you can keep your tokens safer. of course, there are other ways of doing this. in this post, i just have explained how i did it in a monolith application we developed three years ago, and it worked really well.
we keep learning,
alex.
Published at DZone with permission of Alex Soto, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments