Can not get the same signature in section 3.3 of rfc7515 - jwt

In rfc7515, there is a jws example:
BASE64URL(UTF8(JWS Protected Header)) = eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
BASE64URL(JWS Payload) = eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
Its secret key is a jwk:
{"kty":"oct",
"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
}
Then we need to compute the HMAC of the JWS Signing Input ASCII(BASE64URL(UTF8(JWS Protected Header)) || ’.’ || BASE64URL(JWS Payload)) with the HMAC SHA-256 algorithm using the key specified and base64url-encoding the result.
In the jws example, it gives 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk' as the signature, while i get 'ZekyXWlxvuCN9H8cuDrZfaRa3pMJhHpv6QKFdUqXbLc='. Is there anything wrong?
Here is my python3 code.
import hashlib
import hmac
import base64
message = bytes('eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ','ascii')
secret = bytes('AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow','utf-8')
signature = base64.urlsafe_b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest())
print(signature)

I find it's wrong to sign with bytes of the key directly. I should use base64url_decode(key). Then I get the right signature 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk='.
import hashlib
import hmac
import base64
message = bytes('eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ','ascii')
secret = base64.urlsafe_b64decode('AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow==')
signature = base64.urlsafe_b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest())
print(signature)

Related

Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith method instead

How can I sign a JWT with RSA256 algorithm and a private key in Scala?
I want to sign a JWT with specific payload as shown below:
val token = Jwts.builder()
.setPayload(jwtPayload)
.signWith(SignatureAlgorithm.RS256,privateKey)
But it fails with the error:
Exception in thread "main" java.lang.IllegalArgumentException: Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.
at io.jsonwebtoken.lang.Assert.isTrue(Assert.java:38)
My private key looks something like this:
-----BEGIN RSA PRIVATE KEY-----\n....MIIEpGYpYTfFny5xQ8aY5e6Bd3/aC5Ays8wWY2\nsistzq9ARRbvwXzl45HzRKYoWVd1D0D5yOwxFOW++VD3luXH1UpEB1bvxHrpIwbD\ngg9jhO9K1yri6JGA5ENx5InrvKsqodP/llszVJysBlUeAcwMo2gjNA==\n.....-----END RSA PRIVATE KEY-----
Note that mid part of the key has been removed due to security reasons.
I also tried:
val key = privateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+","")
val prvKeyBytes = Base64.getDecoder.decode(key)
val keySpec = new PKCS8EncodedKeySpec(prvKeyBytes)
val kf = KeyFactory.getInstance("RSA")
val prvKey = kf.generatePrivate(keySpec)
val token = Jwts.builder()
.setPayload(jwtPayload)
.signWith(SignatureAlgorithm.RS256,prvKey)
but the above fails at Base64.decode step complaining about invalid base64.

Dart / Flutter : How Do I Sign a String Using an ES 256 Algorithm and Private Key

Since Flutter doesn't support any map APIs across all platforms (mobile and desktop), I'm trying to fetch map snapshots with Apple's Web Snapshots API. This involves constructing a URL with various options then signing the URL. I append the signature to the end of my request URL so Apple can verify that it's from me.
Apple's instructions state:
To generate a signature, sign the string with your private key using a ES256 algorithm (also known as ECDSA using P-256 curve and SHA-256 hash algorithm). The signature must be Base64 URL-encoded.
I don't need to decrypt anything, I just need to sign the string and add it to the end of my request URL. So I don't think I need anything beyond the crypto library included with Flutter.
Here's what I've tried:
import 'package:crypto/crypto.dart';
//Private Key
var key = utf8.encode('''
-----BEGIN PRIVATE KEY-----
abcdef...
-----END PRIVATE KEY-----
''');
var bytes = utf8.encode('My URL String to Sign...');
var hmacSha256 = Hmac(sha256, key);
var sig = hmacSha256.convert(bytes);
var signature = base64UrlEncode(sig.bytes);
I get an unintelligible string as signature and add it to my request URL, but I still get a 401 Not Authorized error, so my signature must be incorrect.
How can I properly sign my URL string with my private key?
Using pointycastle, you need a suitable random number generator instance and a signer initialized with the relevant digest. Then just call generateSignature. That only gets you the r and s values which you need to encode.
Here's an example:
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/asn1.dart';
import 'package:pointycastle/export.dart';
// the private key
ECPrivateKey? privateKey;
// some bytes to sign
final bytes = Uint8List(0);
// a suitable random number generator - create it just once and reuse
final rand = Random.secure();
final fortunaPrng = FortunaRandom()
..seed(KeyParameter(Uint8List.fromList(List<int>.generate(
32,
(_) => rand.nextInt(256),
))));
// the ECDSA signer using SHA-256
final signer = ECDSASigner(SHA256Digest())
..init(
true,
ParametersWithRandom(
PrivateKeyParameter(privateKey!),
fortunaPrng,
),
);
// sign the bytes
final ecSignature = signer.generateSignature(bytes) as ECSignature;
// encode the two signature values in a common format
// hopefully this is what the server expects
final encoded = ASN1Sequence(elements: [
ASN1Integer(ecSignature.r),
ASN1Integer(ecSignature.s),
]).encode();
// and finally base 64 encode it
final signature = base64UrlEncode(encoded);
A huge thanks to Richard Heap for providing the solution. I just wanted to post the final code I settled on for anyone running into this in the future. Only the basic_utils package is needed.
import 'package:basic_utils/basic_utils.dart';
import 'dart:typed_data';
final url = 'My URL string...';
//Convert the URL string to Uint8List
final urlBytes = utf8.encode(url) as Uint8List;
//Prep the private key
var key = '''
-----BEGIN PRIVATE KEY-----
abcdef...
-----END PRIVATE KEY-----
''');
ECPrivateKey privateKey = CryptoUtils.ecPrivateKeyFromPem(key);
//Sign the URL
ECSignature sig = CryptoUtils.ecSign(privateKey, urlBytes, algorithmName: 'SHA-256/ECDSA');
//Convert signature to Base64
final signature = CryptoUtils.ecSignatureToBase64(sig);
I just add that signature to the end of the URL string as required by Apple's API and it works great!

How to Create a JWT in Java with the secret base64 encoded and jose4j

https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOiJsaWh6NiIsInN1YiI6ImxpaHo2IiwiZXhwIjoxNjQ3OTIwNzU3fQ.voGK_UVv0ezNGj42XSDa-x4XojXr-qwmc-VnB_zZKxXHrQZE1agD-YAfeINnTOg0H8XZ3_qdyQGHcUCf5ruHvA
I use E2ABA91===! as key and check secret base64 encoded on jwt.io, then I want to build it with jose4j
I want jose4j get jjwt result,what should I do .
JDK8,
package a;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.NumericDate;
import org.jose4j.keys.HmacKey;
import org.jose4j.lang.JoseException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class D {
static String KEY = "E2ABA91===!";
static String userId = "lihz6";
static Date date = new Date((1647949518L * 1000));
// static Date date = new Date((System.currentTimeMillis()/1000*1000) + 3600 * 1000);
public static void main(String[] args) throws JoseException {
//I want jose4j get jjwt result,what should I do
System.out.println(date.getTime());
jjwt2();//another server use this code and get correct
jose4j2();//I want use jose4j but get error result
}
static void jjwt2() {
Map<String, Object> map = new HashMap<>(16);
map.put("userId", userId);
String biTokentoken = Jwts.builder()
.setClaims(map)
.setSubject(userId)
.setExpiration(date)
.signWith(SignatureAlgorithm.HS512, KEY)
.compact();//jjwt get jwt.io result
System.out.println(biTokentoken);
}
static void jose4j2() throws JoseException {
JwtClaims jwtClaims = new JwtClaims();
jwtClaims.setSubject(userId);
NumericDate now = NumericDate.now();
now.setValue(date.getTime() / 1000);
jwtClaims.setExpirationTime(now);
jwtClaims.setClaim("userId", userId);
HmacKey hmacKey = new HmacKey(KEY.getBytes(StandardCharsets.UTF_8));
JsonWebSignature jsonWebSignature = new JsonWebSignature();
jsonWebSignature.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA512);
jsonWebSignature.setPayload(jwtClaims.toJson());
jsonWebSignature.setKey(hmacKey);
jsonWebSignature.setDoKeyValidation(false);
String biTokentoken = jsonWebSignature.getCompactSerialization();
System.out.println(biTokentoken);
}
}
pom.xml
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.11</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
You're telling jwt.io that your secret is base64-encoded, but then you base64-encode the string yourself when using it in your code.
So, jwt.io decodes the E2ABA91===! string and uses the result as the secret key, and you encode E2ABA91===! and use the result as the secret key. That's why you get different results.
Uncheck the secret base64 encoded on jwt.io and use this in your code:
HmacKey hmacKey = new HmacKey(KEY.getBytes(StandardCharsets.UTF_8));
You should then get the same results in your code and on jwt.io.
The jjwt signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) expects a base64 encoded secret and base64 decodes it prior to use. Jose4j just uses the key it's given. So, to get the same result, you need to base64 decode it first (with a decoder that will ignore the non-base64 character). Replace HmacKey hmacKey = new HmacKey(KEY.getBytes(StandardCharsets.UTF_8)); with HmacKey hmacKey = new HmacKey(Base64.decode(KEY)); to do that.
Also, hopefully you aren't using actual production keys here but E2ABA91===! is much much too short to be a secure HMAC secret.

Decrypt JWT Token in C#

Hello I'm trying to see if they're any JWT token library similar to JOSE-JWT that also works on a Linux machine during runtime.
This is what I currently have in code that decrypts a token and grabs what I need.
private IamTokenDecrypted DecryptToken(string idToken)
{
byte[] key = WebEncoders.Base64UrlDecode(_ssoEncryptionKeyInfo.JsonWebKey.K);
string json = Jose.JWT.Decode(idToken, key, JweAlgorithm.DIR, JweEncryption.A256GCM);
var result = Jose.JWT.Payload(json);
var iamTokenDecrypted = JsonConvert.DeserializeObject<IamTokenDecrypted>(result);
return iamTokenDecrypted;
}
I have a security key and and as you see it has some encryption in it

RSA encryption issue in C#

I have an api that returns me public key. Here is a public key sample,
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ9AMIIBCgKCAQEAoqB1N9kugk4UKYnbh0fcg3qYyYKP0m4B
MjWd05ReeAdj+7JRYDEKO6xthDdVSdRO1/2V+YtY8DnXWnxRaICwu8235s3imZCyqgYnomPWdY+F
K540oTz/zug+9wbrlzt/WQFUU4lPlQbzm/Gjw8XfaCozT0e3bnWQcD7rORCOyuJgwSGgREjTv1ss
pgEaKTMknii9vpGZLeAXwoeIYROhuT4IoIkPDhtY0/UZiCi6v7Ja2dmy53VlWIkcm3rcnSJdvpXr
OgiHvaNABHmeymNycNqd6WUaysBRheluQ86nq/2nZPW0gcvmYt5zbMMYX3yY/n2WtAKeNQBAEW1q
b0s6MwIDAQAB
Now I need to encode string a=1&b=2 using RSA algorithm,
var key = await GetPublicKey();
var keyXml = "<RSAKeyValue><Modulus>" + key + "</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
EncrptedValue = CryptUtils.Encrypt(keyXml, "amount=1&currency=aed", RsaKeyLengths.Bit1024);
I am using CryptUtils class at https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/CryptUtils.cs.
Now I am sending the encrypted value to another server but another server guy telling me that the encrypted value is not corect. What I am doing wrong?