I was just trying out JWT tokens on a rails app, using this jwt library: https://github.com/jwt/ruby-jwt
JWT.encode({sss: "333"}, 'SECRET_KEY')
returns below token:
eyJhbGciOiJIUzI1NiJ9.eyJzc3MiOiIzMzMifQ.CwX_1FztYHVpyx_G27u938SceilsVc5AB5Akwqlo2HA
Then I decoded using the above token
JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJzc3MiOiIzMzMifQ.CwX_1FztYHVpyx_G27u938SceilsVc5AB5Akwqlo2HA", 'SECRET_KEY')
returns below response correctly:
[{"sss"=>"333"}, {"alg"=>"HS256"}]
But if I try to change the last letter of the token to B instead of current A it is still returning the same response which is weird.
JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJzc3MiOiIzMzMifQ.CwX_1FztYHVpyx_G27u938SceilsVc5AB5Akwqlo2HB", 'SECRET_KEY')
Getting this response even though the token I provided is wrong:
[{"sss"=>"333"}, {"alg"=>"HS256"}]
Actually I am getting the same response for all characters up to 'D'
If I use F
and others above then its showing error as expected:
JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJzc3MiOiIzMzMifQ.CwX_1FztYHVpyx_G27u938SceilsVc5AB5Akwqlo2HF", 'SECRET_KEY')
JWT::VerificationError (Signature verification raised) from (irb):34
What could be the reason for this? Is it the expected behavior or am I doing something wrong here?
By design, anyone can decode a JWT and read the contents of the header and payload sections. But we need access to the secret key used to create the signature to verify a token's integrity.
Because JWTs can be signed—for example, using public/private key pairs—you can be sure the senders are who they say they are. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn't been tampered with.
The JWT signature is a hashed combination of the header and the payload. Amazon Cognito generates two pairs of RSA cryptographic keys for each user pool. One of the private keys is used to sign the token. To verify the signature of a JWT token. Decode the ID token.
Regarding your conclusion "the signature is not base64 encoded"; that is not valid. When you base64-decode the signature value, you actually got a decoded value!
The reason is the base64url encoding. The three parts of a JWT are all base64url encoded. Base64 encoding transforms the input data to a 6-Bit representation, mapped to a set of 64 ASCII characters. If you have 3 bytes source data (24 bits), the base64 encoded result is 4 characters long, each character representing a 6 bit value, so 4 * 6 bits = 24 bits. If the number of bits that need to be encoded can't be divided by 6 without remainder, there'll be one character more with 2 or 4 insignificant bits.
In your case, the encoded signature has 43 characters, which means 43 * 6 = 258 bits. So you could theoretically encode 258 bits, but the signature is only 256 bits (32 bytes) long, which means there are 2 insignificant bits on the end.
A look on the base64 encoding table shows that 'A' to 'D' represent the 6 bit values 0 (000000) to 4 (000011), so the first four bits, which are still significant, are all identical, and only the last two, insignificant bits are changing. But the character 'E' stands for 5 (000100) and would change the last bit of the 256 bit value.
The following table illustrates that. It shows the last 4 base64 characters of the signature, including the possible changes of the last character (A-D) and the bit and byte number of the original data: The change of the last character in that range only causes a change of the last two bits (light grey) but does not change the original data, because the changed bits are beyond the last bit of the original data.
If you're really concerned about the 2 bits on the end, you can consider to change the signature algorithm to HS384.
Then you have a 384 bit (= 48 byte) hash, which is represented in 64 Base64 characters. 384 can be divided by 8 and by 6 without remainder, so there are no insignificant bits on the end and any change on the last character will lead to a failed verification.
HS512 would have the same "problem" as HS256, and then even 4 insignificant bits on the end, but nonetheless a longer hash (512 bits vs. 384 bits vs. 256 bits) is considered more secure.
Conclusion: it's all fine, nothing wrong here. The verification of a signature is based on its binary value, which is not affected by the peculiarities of the encoding. You can change the algorithm, if you're worried, but I think it's not really necessary and the choice of an algorithm should not be based on that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With