Verification key for jose4j JwtConsumer - java

I am using jose4j to validate and process a JWT. The JWT looks like the following and it passes the validation in the JWT homepage.
However, I can't do the same using jose4j java library. The exception complains about the verification key I set. But there are many types of keys defined in the library and I tried them but no luck. The code is as following:
import java.util.Map;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.HmacKey;
public class YGJWT {
public static void main(String args[]) throws InvalidJwtException {
String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
String secret = "secret";
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setVerificationKey(new HmacKey(secret.getBytes())) //what kind of key do i need to use it here?
.build();
JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
Map<String, Object> claimsMap = jwtClaims.getClaimsMap();
claimsMap.forEach((String key, Object val) -> {
System.out.println(key + ": " + val.toString());
});
}
}
Any help is appreciated.

I 'd guess you're getting an Exception something like this?
org.jose4j.lang.InvalidKeyException: A key of the same size as the hash output (i.e. 256 bits for HS256) or larger MUST be used with the HMAC SHA algorithms but this key is only 48 bits
The HmacKey is the correct type for HS256 but the key is technically too short according to the second paragraph of https://www.rfc-editor.org/rfc/rfc7518#section-3.2 which has the same text as the exception message.
You can work around it by building the JwtConsumer with .setRelaxVerificationKeyValidation(), which will allow for shorter keys.
That looks like this (adding just one line to a snippet from your example):
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setVerificationKey(new HmacKey(secret.getBytes()))
.setRelaxVerificationKeyValidation()
// allow shorter HMAC keys when used w/ HSxxx algs
.build();
In general though I would try and avoid the use of a short password like key such as "secret" and suggest using a stronger key when possible.

Related

Cannot get JWT token generation to work on jwt.io debugger

I am having some trouble using the JWT debugger at https://jwt.io/ I am using the JWT library from https://github.com/jwtk/jjwt The use case is to send requests to an API that requires that I pass an API key in the token payload and sign it using the client secret that they give me. So far, using the client secret they sent me (one secret appeared to be base64, but the second one they sent was definitely not base 64). So far generating the token using the above library fails on the REST API, and it also fails at jwt.io. If I can get that working, I can probably get it to work on the REST API as well.
Here is a block of code I use to generate my code. I used a very simple secret string for this example but it is 32 bytes long (the client secret they gave me was 43 bytes long):
public class TestHarness {
public static void main(String[] args) {
String apiKey = "39999999-ba25-476a-957e-806f9f726e39";
String secret = "123456789-123456789_123456789_12";
//long timeNow = JWTutil.getUnixTime();
// Using same time so that each time we run we should get the exact same
// token
long timeNow = 1672761664338l;
System.out.println(String.format("Now in UNIX is %d", timeNow));
String token = JWTutil.getToken(apiKey, secret, 300, timeNow);
System.out.println(token);
String[] parts = token.split(token, '.');
System.out.println(String.format("Token has %d parts", parts.length));
for (int i=0;i<parts.length;i++) {
System.out.println(String.format("Part %d : %s", i, parts[i]));
}
if (parts.length > 2) {
System.out.println(parts[2]);
}
}
And here is my JWT utils class with the methods used above to generate the token
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.nio.charset.StandardCharsets;
import java.util.*;
import javax.crypto.SecretKey;
public class JWTutil {
private static SignatureAlgorithm sigHS256 = SignatureAlgorithm.HS256;
public static String getToken(String appID, String secret, int duration ) {
long currTime = getUnixTime();
return getToken(appID, secret, duration, currTime);
}
public static String getToken(String appID, String secret, int duration, long currTime) {
String token = Jwts.builder().setHeader(getHeader()).addClaims(getClaims(appID, currTime, duration)).signWith(getSigningKey(secret), sigHS256).compact();
return token;
}
private static Map<String,Object> getHeader() {
Map<String, Object> map = new HashMap<String,Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
return map;
}
private static Map<String,Object> getClaims(String appId, long now, int duration) {
Map<String,Object> claims = new HashMap<String,Object>();
claims.put("sub", appId);
claims.put("iat", Long.valueOf(now));
claims.put("exp", Long.valueOf(now + (long)duration));
return claims;
}
private static Key getSigningKey(String secret) {
byte[] theBytes = secret.getBytes(StandardCharsets.UTF_8);
//byte[] encodedBytes = Base64.getEncoder().encode(secret.getBytes(StandardCharsets.UTF_8));
//Key key = new SecretKeySpec(encodedBytes, sigHS256.getJcaName());
SecretKey key = Keys.hmacShaKeyFor(theBytes);
return key;
}
public static long getUnixTime() {
Date current = new Date();
return current.getTime();
}
}
I did look at the source on JJWT (I couldn't find the link to proper JavaDocs but the source comments on each function served the same purpose), the signWith() function takes 1 or 2 arguments - either a Key (1 argument) which will "guess" the appropriate algorithm to use, or 2 args - A key and the signing algorithm (presumably for when you want to explicitly set the signature). In an older post, it quoted signWith() taking 1 arg as the Signing algorithm and the second as a base64 string but I seem to recall that is deprecated - it's a key or nothing.
On jwt.io, I provide the client secret I used to create the secret with. I suspect that I may be misunderstanding what I'm supposed to provide. When answering, assume that I'm given a human readable string which may or may not already be Base64 encoded. When I toggle the Base64 Encoded Secret checkbox on jwt.io, the signature changes - both ways to something other than what was in original token, even with the text staying the same for the key.
I think I discovered the problem. I looked at the code sample that K. Nicholas provided. The Nimbus-Jose library is one that I had previously tried. However the problem with the jwt.io site was more low-tech than that. WHen entering your client secret, make sure you erase the text that says "your 256 bit secret".
I thought it was an underlay but no it's actual text content. If you paste your secret in that box, it just prepends it to the text "your 256 bit secret".
In this case the solution was very low tech.

Encryption/Decryption in flutter by adapting java code

I am trying to create an app in flutter and the sample code which I have is in java.
Here is the sample java code gist
https://gist.github.com/kapiljhajhria/72a22ff75e238878f539f7bb21026208
and here is my flutter code gist
https://gist.github.com/kapiljhajhria/795d1a7c7cf1c76ca8e327bf8b2f51de
Here is a brief summary of what I am doing
Generate a unique Session Key: AES Random Key 256
Encrypt JSON data using the Session Key from step 1
Generate SHA256 hash of JSON data
Encrypt generated hash from step 3, using session key from step 1.
Encrypt the session key using the public key. Public key provided as certificate.cer file. I copied the String value and added it to the class as a constant in order to make it easier to use. Not sure if this was the best approach.
Created a POST request with 3 Parameters. As shown in the java code. I think I am doing this part correctly.
The response which I will get will be encrypted using the session key from step 1. So i will have to decrypt that response data. Haven't reached this step yet.
I don't have access to the server where this request is being made.
Since the post request is being made using web view, I can't figure out a way to get proper error from my request. All I get is web page which says "Invalid Request"
So My first guess is that I am not using public key properly to encrypt my session key.
if that part is correct, then I am not encrypting the data properly or my encryption method doesn't match encryption methods used in java code the java code. Maybe the session key which I am generating is not correct.
Any help would be greatly appreciated. Thank you. If you need anything from me then please let me know.
I used this document as my reference: https://developers.emsigner.com/signer-gateway/api-reference/signing-documents.html
You need 2 packages: pointycastle and x509, and to import them as follows:
import 'package:pointycastle/export.dart';
import 'package:x509/x509.dart';
Then you need these helper functions:
Uint8List generateSessionKey() {
final r = Random();
return Uint8List.fromList(List<int>.generate(32, (_) => r.nextInt(256)));
}
RSAPublicKey parseCert(String pemData) {
final cert = parsePem(pemData).first as X509Certificate;
final pub = cert.publicKey as RsaPublicKey;
return RSAPublicKey(pub.modulus, pub.exponent);
}
Uint8List encryptUsingPublicKey(RSAPublicKey key, Uint8List data) {
final cipher = PKCS1Encoding(RSAEngine())
..init(true, PublicKeyParameter<RSAPublicKey>(key));
return cipher.process(data);
}
Uint8List encryptUsingSessionKey(Uint8List key, Uint8List data) {
final cipher = PaddedBlockCipher('AES/ECB/PKCS7')
..init(true, PaddedBlockCipherParameters(KeyParameter(key), null));
return cipher.process(data);
}
Uint8List sha256Digest(Uint8List data) {
return SHA256Digest().process(data);
}
And you'd build your 3 parameters like this:
final pem = File('cert2.pem').readAsStringSync();
final publicKey = parseCert(pem);
final sessionKey = generateSessionKey();
final encryptedSessionKey = encryptUsingPublicKey(publicKey, sessionKey);
final jsonString = json.encode(<String, dynamic>{
'FileType': 'PDF',
'SignaturePosition': 'Top-Left',
'AuthToken': 'some token',
'File': '',
'SUrl': 'http://localhost:3000/Success',
'FUrl': 'http://localhost:3000/Error',
'CUrl': 'http://localhost:3000/Cancel',
'ReferenceNumber': 'generate unique reference number',
});
final jsonBytes = utf8.encode(jsonString) as Uint8List;
final encryptedJson = encryptUsingSessionKey(sessionKey, jsonBytes);
final hash = sha256Digest(jsonBytes);
final encryptedHash = encryptUsingSessionKey(sessionKey, hash);
final p1 = base64.encode(encryptedSessionKey);
final p2 = base64.encode(encryptedJson);
final p3 = base64.encode(encryptedHash);
BUT, the big problem I see is how you then do the POST, because you want to be in a web page, right? And the normal flutter web view doesn't support an initial post. It does look like there's another package. Just search for flutter webview post.
By the way, if you don't want to use the pointycastle registry, you can rewrite encryptUsingSessionKey without as:
final cipher = PaddedBlockCipherImpl(
PKCS7Padding(),
ECBBlockCipher(AESEngine()),
)..init(true, PaddedBlockCipherParameters(KeyParameter(key), null));
return cipher.process(data);
Finally, at least until you get the web view issue understood, you can just use http to do the post. But, let it do the work of encoding the parameters and setting the content type, as follows:
final response = await http.post(
Uri.parse('https://somewhere/V3_0/Index'),
body: <String, String>{
'Parameter1': p1,
'Parameter2': p2,
'Parameter3': p3,
},
);
print(response.statusCode);
print(response.body);
Have a look at this project, It's an example for the Encryption by Flutter with a good documentation, have 3 types of encryptions:
AES encryptions
Fernet encryptions
Salsa20 encryptions

Keycloack is a MessageDigestPasswordEncoder sha512 exists?

I am in the process of migrating users from an OAuth 2 system made with Symfony to Keycloak.
Create the users in Keycloak with the encrypted password is ok, but I can't find an algorithm equivalent to mine.
example of user creation:
Post
{
"firstName": "test_encryption",
"lastName":"test_encryption",
"email":"jeremy.rafflin.test#ageo.fr",
"credentials": [{
"type":"password",
"secretData":"{\"value\":\"zeR2Uapc+a/2qD5QR56gh3mVb+KOeZ2XU+rkWMK6B5A=\",\"salt\":\"OThjajM1WnVZWlI3UzZOLk12WjJsQS9VWWZXQXp0WGZGLm5tL2hGSVFzbw==\"}​​​​​​​",
"credentialData": "{\"algorithm\":\"sha512\",\"hashIterations\":5000}"
}]
}
For the current encryption in PHP I am using https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php.
Which amounts to:
$password = 'toto';
$salt = '1234';
$salted = $password.'{'.$salt.'}';
$digest = hash('sha512', $salted, true);
for ($i=1; $i<5000; $i++) {
$digest = hash('sha512', $digest.$salted, true);
}
$encodedPassword = base64_encode($digest);
In the spring documentation, https://andifalk.github.io/reactive-spring-security-5-workshop/workshop-tutorial.html I see :
package org.springframework.security.crypto.factory;
public class PasswordEncoderFactories {
...
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
...
}
So I thought my algorithm matched MessageDigestPasswordEncoder ("SHA-512") and tried to create a user, but it doesn't work:
"credentialData": "{"algorithm":"SHA-512","hashIterations":5000}"
Does my algorithm exist in keycloak or do I have to create a custom credential algorithm ?
Does my algorithm exist in keycloak or do I have to create a custom
credential algorithm ?
From the Keycloak Documentation:
Here’s an explanation of each policy type:
HashAlgorithm
Passwords are not stored as clear text. Instead they are
hashed using standard hashing algorithms before they are stored or
validated. The only built-in and default algorithm available is
PBKDF2.
Nevertheless, Keycloak allows you to customized and deploy your own algorithm by taking advantage of Service Provider Interfaces.

iOS Elliptical curve key used for encryption decryption with Java Backend

I am a iOS developer and trying to use Secure enclave to generate ECC pair key. I am able to do that successfully using the sample app here: https://github.com/agens-no/EllipticCurveKeyPair. When I use this key along with a Python implementation to do encryption and decryption mentioned here: https://gist.github.com/dschuetz/2ff54d738041fc888613f925a7708a06 it works.
The problem is that i need a Java code to do the same. Can anyone help me to achieve this or point me to a code that does the same job of as Python code is doing.
On iOS side I am doing eciesEncryptionStandardX963SHA256AESGCM encrypt and decrypt logic.
I know i should have tried to solve this myself. But I am a iOS Engineer and trying my hands on Java backend. Would be really helpful if someone can guide me.
Created a sample Java code based on the answer. Link to code: https://gist.github.com/balrajOla/fa2f6030538b20a396c086377a6f7114
Using the sample iOS App provided here: https://github.com/agens-no/EllipticCurveKeyPair. I generated ECC keys.
Then pass the public key to the Java code to create an encrypted message. This encrypted messages is passed back to sample iOS app mentioned above to be decrypted using eciesEncryptionStandardX963SHA256AESGCM algo.
But we get an error mentioned below snapshot.
We had the same problem. We want to bring a EC key exchange working from iOS secure enclave with a Java Back-end.
After three days of trial&error, we finally found a Java implementation which is working.
Java code was taken from https://github.com/O2-Czech-Republic/BC-ECIES-for-iOS
And the iOS code, using eciesEncryptionCofactorVariableIVX963SHA256AESGCM algorithm:
static func getExportableKeyFromECKey() -> String? {
// If exists already a created EC Key, then export the public part
if let privateKey = self.loadECPrivateKey() {
if let publicKey = self.getECPublicKey(privateKey) {
if self.algorithmAcceptedForEC(publicKey) {
var error: Unmanaged<CFError>?
// Get Public key External represenatation
guard let cfdata = SecKeyCopyExternalRepresentation(publicKey, &error) else {
return nil
}
let pubKeyData: Data = cfdata as Data
return pubKeyData.base64EncodedString()
}
}
}
// If no EC Key created, then first create one
else {
var error: Unmanaged<CFError>?
let tag = Config.skName.data(using: .utf8) ?? Data()
let attributes: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: Config.skType,
kSecAttrKeySizeInBits as String: Config.ecKeySize,
kSecPrivateKeyAttrs as String: [ kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag]]
do {
// Create Private Key
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
// Get Public Key
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw error!.takeRetainedValue() as Error
}
// Get Public key External represenatation
guard let cfdata = SecKeyCopyExternalRepresentation(publicKey, &error) else {
throw error!.takeRetainedValue() as Error
}
let pubKeyData: Data = cfdata as Data
return pubKeyData.base64EncodedString()
} catch {
print(error)
}
}
return nil
}
static func loadECPrivateKey() -> SecKey? {
let tag = Config.skName.data(using: .utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: Config.skType,
kSecReturnRef as String: true]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
return nil
}
print("LOAD PRIVATE KEY: \n \(item as! SecKey) \n")
return (item as! SecKey)
}
static func getECPublicKey(_ privateKey: SecKey) -> SecKey? {
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
// Can't get public key
return nil
}
return publicKey
}
static func algorithmAcceptedForEC(_ publicKey: SecKey) -> Bool {
guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, Config.ecAlgorithm) else {
// Algorith not supported
print("\nEncrytion Algorithm not supported!!!\n")
return false
}
return true
}
/// if let encryptedData = Data(base64Encoded: "BOqw779hxsGLMEV7X81Mphcx+SMtxSQs388s5CydkvJ4V2XuuWoyp48GCmgDMBnYlEIRqAdHxIc/Ts3ATxa9ENCDGdIZf5CjpWsOIVXYxLvupdap4w==", options:.ignoreUnknownCharacters)
static func decryptStr(_ encData: Data) {
/// 1. Step: Get the Private Key and decrypt the symmetric key
let privateKey = loadECPrivateKey()
guard SecKeyIsAlgorithmSupported(privateKey!, .decrypt, Config.ecAlgorithm) else {
print("Can't decrypt\nAlgorithm not supported")
return
}
DispatchQueue.global().async {
var error: Unmanaged<CFError>?
let clearTextData = SecKeyCreateDecryptedData(privateKey!,
Config.ecAlgorithm,
encData as CFData,
&error) as Data?
DispatchQueue.main.async {
guard clearTextData != nil else {
print("Can't decrypt")
return
}
let clearText = String(decoding: clearTextData!, as: UTF8.self)
print("Decrypted Info: \(clearText)")
// clearText is our decrypted string
}
}
}
In Java you have two interesting classes - ECGenParameterSpec and KeyPairGenerator. ECGenParameterSpec specifies parameters for generating elliptic curve domain parameters, and KeyPairGenerator is used to generate pairs of public and private keys.
In the book Android Security Internals by Nokilay Elenkov there's a good code example of their combination to generate the key pair.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDH");
ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("secp256r1");
kpg.initialize(ecParamSpec);
KeyPair keyPair = kpg.generateKeyPair();
This is the explanation given about the previous code
There are two ways to initialize a KeyPairGenerator: by specifying the
desired key size and by specifying algorithm-specific parameters. In
both cases, you can optionally pass a SecureRandom instance to be used
for key generation. If only a key size is specified, key generation
will use default parameters (if any). To specify additional
parameters, you must instantiate and configure an
AlgorithmParameterSpec instance appropriate for the asymmetric
algorithm you are using and pass it to the initialize() method, as
shown in Example 5-15. In this example, the ECGenParameterSpec
initialized in line 2 is an AlgorithmParameterSpec that allows you to
specify the curve name used when generating Elliptic Curve (EC)
cryptography keys. After it is passed to the initialize() method in line 3,
the subsequent generateKeyPair() call in line 4 will use the specified
curve (secp256r1) to generate the key pair.

Unable to get UniqueIdentifier from X500Principal of X509Certificate

I wanted to get the unique identifier of a X509Certificate using Java.
I tried to get the value from using the below code:-
java.security.cert.X509Certificate certificate=// certificate object
certificate.getSubjectX500Principal().getName();
But i am unable to get the unique identifier value alone.This is the value i am getting:-
2.5.4.45=#0309000000db000000a01a,OU=06
I wanted to get the value alone for "2.5.4.45".
I also tried to get the value using the below code:-
String dn2 = certificate.getSubjectX500Principal().getName();
LdapName ldapDN;
ldapDN = new LdapName(dn2);
for(Rdn rdn: ldapDN.getRdns()) {
System.out.println(rdn.getType() + " -> " + rdn.getValue());
if(rdn.getType().equalsIgnoreCase("2.5.4.45")){
System.out.println(rdn.getValue());
}
I am getting an object as the value for unique identifier. I am not able to parse the Object, get the value for this.
Update ::
I am still not able to figure out a way to get the UniqueIdentifier identifer.Any help is appreciated.
You need provide set of known OIDs. Then you will get a human readable value of DN String. Value of known OIDs will be readable when you define OID. For example:
Map<String, String> knownOids = new HashMap<String, String>();
knownOids.put("2.5.4.45", "uniqueIdentifier");
String humanReadableDN = certToken.getCertificate().getSubjectX500Principal().getName(X500Principal.RFC2253, knownOids);
Example OID repository you can find here: http://oid-info.com/get/2.5.4.45
For Example, this:
CN=Krzysiek,1.2.840.113549.1.9.1=#160f3334353334354064666766642e706c
Will be translated to this:
commonName=Krzysiek,emailAddress=345345#dfgfd.pl
When you provide a set with:
knownOids.put("1.2.840.113549.1.9.1", "emailAddress");

Categories