Introduction.
In our previous guide JWT for Developers: Behind the Scenes, we talked about JWTs signed with HMAC using SHA256. This time, we'll focus on the RS256 algorithm, which uses the PKCS#1 RSASSA v1.5 specification with SHA256. Don't worry about these complicated names; just remember it's called RS256 😌.
What is RS256 🔒?
RS256 is a digital signature algorithm that uses public key cryptography. This algorithm is part of the RSA family and uses SHA-256 as hash function. In practice, RS256 generates a pair of keys: one public and one private. Here is a breakdown of how it works:
Private Key: The private key is used to create and sign messages. Only the owner of the private key can generate valid signatures for messages.
A private key in format PEM looks like this 🔑:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICzTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIS28pq8UusVcCAggA
mTE6uS7rjd2rSc/n4i6gTYniJtLfOUzTK8HXFXxE2nTT7vcbsi9yXMH2zAA8JQSg
r5v8TRs3B0rJQaOMLBu9bWqmS6GZLNdPmTYVq6Y0YBMyMwMusCdDjUyuu7R2h+1L
+XT0XTwLnJIlXU8wI4EsPhHyDHLNUwxPkSxrCwo9kTv1C9OdCUvIFtpM7mhCmXiX
a3JOr547mmXo2aJH8sKb/ANON6wa0Zq8vgGQxLivXQRgDja3EdEu5mFkHZmJB8Cd
DSE4FPtKmiz5wKJ4E4ZdKsKpdBCSgjHkY5mU4ut+3st1zBEgDMLCTsyDOG+FAtap
6Q==
-----END ENCRYPTED PRIVATE KEY-----
Public Key: The public key is freely distributable and is used exclusively to verify the authenticity of messages signed with the private key. This ensures that anyone in possession of the public key can confirm that a message was signed by the owner of the private key, thus guaranteeing its integrity and authenticity.
A public keyin format PEM looks like this 🔑:
-----BEGIN PUBLIC KEY-----
MIGbMA0GCSqGSIb3DQEBAQUAA4GJADCBhQJ+AKwTaUz3u/k8+P/ZZqf+Zr+nWLx3
7nLpkQZRDoBDs8RoVLGiGOcfydUniSpMpfTTYih8+Wl9RPNXlhJ1oHaIyD8WbKy8
NfY2l8NruixdAgMBAAE=
-----END PUBLIC KEY-----
Use case 🧰.
There are many use cases for public key signing, but we will focus on the case of OAuth2.0 with an Azure AD server. When the server issues an access token, it also provides a public URL with the public key. This allows us to validate the token's authenticity in our resources.
- The private key must be stored very carefully to avoid exposure.
- In our case, we saw how a developer tried to modify the token's content but failed the verification check.
- Other developers enjoy a stress-free life by using the public key solely to verify the authenticity of the token received from Azure.
Benefits ✅.
This approach is fantastic because it allows us to validate tokens, but not create them on behalf of the Azure server. If a malicious actor tried to alter our token while it is in transit, we could immediately detect and verify that the token has been tampered with and reject it.
Take a Moment to Read the Above Section ☕.
If you've made it this far, you're ready to create your own token signing system using a public key algorithm. I hope you're excited 🙋🏻!
Let's Get Started
First things first: we're going to use Node.js's native crypto module. If you're familiar with other programming languages, feel free to use their native cryptography modules; they probably have one.
- Generate RSA Keys: We use generateKeyPairSync to generate an RSA key pair. The private key is encrypted with a passphrase for secure storage.
const { generateKeyPairSync, createSign, createVerify } = require('crypto');
// 1. Generate Keys RSA
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
modulusLength: 2048, // key Size
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: 'top secret'
}
});
- Creating our funtion to make signed tokens
function createToken(payload, privateKey) {
const header = {
alg: 'RS256',
typ: 'JWT'
};
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64').replace(/=/g, '');
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64').replace(/=/g, '');
const token = `${encodedHeader}.${encodedPayload}`;
const sign = createSign('RSA-SHA256');
sign.update(token);
sign.end();
const signature = sign.sign(privateKey, 'base64').replace(/=/g, '');
return `${token}.${signature}`;
}
- Now we will verify the tokens signed by us.
function verifyToken(token, publicKey) {
const [encodedHeader, encodedPayload, signature] = token.split('.');
const verify = createVerify('RSA-SHA256');
verify.update(`${encodedHeader}.${encodedPayload}`);
verify.end();
const isValid = verify.verify(publicKey, signature, 'base64');
if (isValid) {
const payload = JSON.parse(Buffer.from(encodedPayload, 'base64').toString('utf8'));
return payload;
} else {
throw new Error('Token verification failed');
}
}
Remember, security is a journey, not a destination. There is always more to learn and new techniques to explore.
Thank you for following along. Happy coding, and keep learning! Your journey in the fascinating world of cryptography has just begun. 🚀
Top comments (1)
amazing