I am using jersey rest webservice along with JWT with RSA signature token feature for authentication. I was able to successfully create and send token to the front-end. Now after I have achieved this far I am confused about verifying token and also identifying a user making request for resources.
Few questions here:
Do I have to decode the jwt token received on front-end to check the
claims?
How do I identify a user requesting for a resource on backend?
Because on few of the posts on SO some people said its not required to decode the token on front end (check this link) while other examples on other sites shows example of decoding the token on front end such as this
Now I am confused how to go further on whether I should actually decode a token on front-end or leave it as is? If so how come other examples show decoding on front-end like this:
angular.module('app')
.factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) {
function urlBase64Decode(str) {
var output = str.replace('-', '+').replace('_', '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw 'Illegal base64url string!';
}
return window.atob(output);
}
function getClaimsFromToken() {
var token = $localStorage.token;
var user = {};
if (typeof token !== 'undefined') {
var encoded = token.split('.')[1];
user = JSON.parse(urlBase64Decode(encoded));
}
return user;
}
Token example I am using here:
private void authenticate(String email, String password)
throws Exception {
try {
Connection con = DBConnection.getConnection();
PreparedStatement statement = con.prepareStatement("select USR_PRIMARY_EMAIL, USR_PASSWORD from TBL_USER where USR_PRIMARY_EMAIL=? and USR_PASSWORD=?");
statement.setString(1, email);
statement.setString(2, password);
ResultSet result = statement.executeQuery();
if (result.next()) {
System.out.println("User authenticated successfully");
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(1024);
KeyPair kp = keyGenerator.genKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) kp.getPrivate();
JWSSigner signer = new RSASSASigner(privateKey);
JWTClaimsSet claimsSet = new JWTClaimsSet();
claimsSet.setSubject("alice");
claimsSet.setIssuer("https://c2id.com");
claimsSet.setExpirationTime(new Date(new Date().getTime() + 60 * 1000));
System.out.println("publicKey is: " + publicKey);
System.out.println("privateKey is: " + privateKey);
System.out.println("claimsSet is: " + claimsSet);
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256),claimsSet);
signedJWT.sign(signer);
token = signedJWT.serialize();
System.out.println("Token is: " + token);
signedJWT = SignedJWT.parse(token);
System.out.println("signedJWT is: " + signedJWT);
JWSVerifier verifier = new RSASSAVerifier(publicKey);
assertTrue(signedJWT.verify(verifier));
assertEquals("alice", signedJWT.getJWTClaimsSet().getSubject());
assertEquals("https://c2id.com", signedJWT.getJWTClaimsSet().getIssuer());
assertTrue(new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime()));
} else {
System.out.println("User doesn't exist");
}
} catch (Exception e) {
System.out.println("DB related Error");
e.printStackTrace();
}
}
Also one more issue is the token generated using nimbus+jose_JWT(RSA signature) I am not able to decode in angular auth0 library. Is it because I am using public key?
Do I have to decode the jwt token received on front-end to check the claims?
Yes. The JWT claims set is base64URL-encoded JSON, so you need to decode in order to read it.
How do I identify a user requesting for a resource on backend?
The sub claim is optional, but practically every JWT provider will issue all tokens with a subject ID that identifies the requester. From the JWT spec:
The "sub" (subject) claim identifies the principal that is the
subject of the JWT. The claims in a JWT are normally statements
about the subject. The subject value MUST either be scoped to be
locally unique in the context of the issuer or be globally unique.
The processing of this claim is generally application specific. The
"sub" value is a case-sensitive string containing a StringOrURI
value. Use of this claim is OPTIONAL.
Also one more issue is the token generated using nimbus+jose_JWT(RSA signature) I am not able to decode in angular auth0 library. Is it because I am using public key?
No. All JWT claim sets are base64URL-encoded JSON independently of signature method, so you should be able to decode it.
Related
I am trying to connect my Metamask wallet to my Java Spring-Boot backend. I was trying to follow the example here. I am able to autogenerate the nonce and receive the wallet ID without a problem. I am trying to verify the signed nonce from the Wallet on the server to make sure that the sender is indeed who they say they are. However, I am unable to find any documentation on Web3J to do this.
Is web3j not the right package to use for this? The example shows how to do the verification on NodeJS based on javascript but I don't find any example on how to do this on Java.
My understanding is that the public key is the wallet ID itself and that the message is the nonce signed by the private key of the wallet which is not shared for obvious reasons. According to this, I would need to "decrypt" the message using the public key and see if the decrypted message is same as the nonce that the backend sent to Metamask to sign. Is this correct?
Here is my code to create and send the nonce to UI:
public User findUserByPublicAddress(String publicWalletId) {
User u = userRepository.findByPublicWalletId(publicWalletId);
if(u == null) {
u = new User("", "", "", null, publicWalletId, "");
String nonce = StringUtil.generateRandomAlphaNumericString();
u.setNonce(nonce);
userRepository.saveAndFlush(u);
}
return u;
}
Here, I see if the user is already in my system and if they are not, then I just create a temporary user with a random nonce generated and saved in the DB. This nonce is sent to the UI for Metamask to sign. However, I am not sure how to do the verification part of it.
I was able to figure this out finally. My initial understanding was incorrect. I was not supposed to attempt to decrypt the message to retrieve the nonce. Rather I needed to use the nonce to see if I can retrieve the public key of the private key used to sign the message and see if that public key retrieved matches the wallet ID.
The algorithm:
Receive the signed message and the wallet ID from the client
Retrieve the nonce sent to the client with the same wallet ID
Generate the hash of the nonce
Generate the signature data from the message. This basically retrieves the V, R and S and. R and S are the outputs of the ECDSA Signature and V is the Recovery ID.
Using the ECDSA Signature and Hash of the Nonce, generate the possible public Key that was used to sign the message. At max, one will be able to generate 4 possible public keys for this message.
Check if any of the generated keys match public wallet ID that the client sent. If it matches, then we have a positive match. Generate the JWT and respond to the client. If not, we know that the nonce was not signed by the Metamask wallet we expected.
The Code:
Here is a sample code for UI (JavaScript and HTML):
web3.eth.sign(
web3.utils.sha3(nonce),
window.userWalletAddress)
.then((message) => {
console.log(message)
data['message'] = message // BODY
var xmlReq = new XMLHttpRequest();
xmlReq.onreadystatechange = function() {
if(this.readyState == 4 && this.status == 200) {
response = this.responseText
console.log(response)
}
};
xmlReq.open("POST", "/api/users/login", true)
xmlReq.setRequestHeader('Content-Type', 'application/json')
xmlReq.send(JSON.stringify(data))
})
The web3.eth.sign() takes the message to be signed and takes the wallet ID that is signing it. This is then sent to the backend. In the backend:
public User signin(UserLoginDTO loginDetails, HttpServletResponse response) {
try {
// Get the wallet ID and signed message from the body stored in the DTO
String publicWalletId = loginDetails.getPublicWalletId();
String message = loginDetails.getMessage();
// Find the nonce from the DB that was used to sign this message
User user = userRepository.findByPublicWalletId(publicWalletId);
String nonce = user.getNonce();
// Generate the HASH of the Nonce
byte[] nonceHash = Hash.sha3(nonce.getBytes()) // org.web3j.crypto.Hash
// Generate the Signature Data
byte[] signatureBytes = Numeric.hexStringToByteArray(message); // org.web3j.utils.Numeric
byte v = (byte) ((signatureBytes[64] < 27) ? (signatureBytes[64] + 27) : signatureBytes[64]);
byte[] r = Arrays.copyOfRange(signatureBytes, 0, 32);
byte[] s = Arrays.copyOfRange(signatureBytes, 32, 64);
SignatureData signatureData = new SignatureData(v, r, s); // org.web3j.crypto.Sign.SignatureData
// Generate the 4 possible Public Keys
List<String> recoveredKeys = new ArrayList<>();
for(int i = 0; i < 4; i++) {
BigInteger r = new BigInteger(1, signatureData.getR());
BigInteger s = new BigInteger(1, signatureData.getS());
ECDSASignature ecdsaSignature = new ECDSASignature(r, s);
BigInteger recoveredKey = Sign.recoverFromSignature((byte)i, ecdsaSignature, nonceHash);
if(recoveredKey != null) {
recoveredKeys.add("0x" + Keys.getAddressFromKey(recoveredKey)); // org.web3j.crypto.Keys
}
}
// Check if one of the generated Keys match the public wallet ID.
for(String recoveredKey : recoveredKeys) {
if(recoveredKey.equalsIgnoreCase(publicWalletId)) {
// Add Code here to create the JWT and add that to your HttpServletResponse. Not shown here.
return user;
}
}
throw new CustomException("Message Sign Invalid", HttpStatus.UNAUTHORIZED);
}
catch (Exception ex) {
// Custom Error Handling.
}
}
I'm trying to sign the message with a detached payload using the Nimbus JOSE JWT library in Java. The verification goes through locally but whenever I try to send it to the server using Postman I get: "The signature header x-jws-signature was parsed and has a valid JOSE header that complies with the specification. However, the signature itself could not be verified"
JWSSigner signer = new RSASSASigner(privateKey);
HashMap<String, Object> criticalParameters = new HashMap<>();
criticalParameters.put("http://openbanking.org.uk/iat", 1501497671);
criticalParameters.put("http://openbanking.org.uk/iss", orgId);
criticalParameters.put("http://openbanking.org.uk/tan", "openbankingtest.org.uk");
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.PS256)
.type(JOSEObjectType.JOSE)
.keyID(keyID)
.criticalParams(criticalParameters.keySet())
.customParams(criticalParameters)
.build();
// With encoding the payload
JWSObject jwsObject = new JWSObject(header, payload);
jwsObject.sign(signer);
String jws = jwsObject.serialize(true);
JWSObject parsedJWSObject = JWSObject.parse(jws, payload);
if (parsedJWSObject.verify(new RSASSAVerifier(publicKey, criticalParameters.keySet()))) {
System.out.println(parsedJWSObject.serialize(true));
} else {
System.out.println("Invalid");
}
//=============================
// Without encoding the payload
Base64URL signature = signer.sign(header, (header.toBase64URL().toString() + "." + payload).getBytes());
JWSVerifier verifier = new RSASSAVerifier(publicKey, criticalParameters.keySet());
boolean isValid = verifier.verify(header, (header.toBase64URL().toString() + "." + payload).getBytes(), signature);
System.out.println(header.toBase64URL().toString() + ".." + signature.toString());
System.out.println(isValid);
//=============================
Both of the functions successfully sign and verify the JWS but for some reason, it doesn't work. If it helps, I'm trying to access the Open Banking API.
Got a similar problem very recently. I would suggest you to check the following:
Is the payload in the request exactly the same as the one used for the JW signature (without escaping or formatting characters)?
What's the order of the JSON properties in the payload and does the financial entity you are trying to interact with have specific requirements when it comes to the order of those JSON fields?
I know it's very questionable to expect the json properties in the payload to be in a specific order, but by experience I found out that some open banking implementations are assuming a specific order (not even alphabetical) and they will fail with that error when the order is not the one they expect.
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.PS256)
.type(JOSEObjectType.JOSE)
.keyID(keyID)
.criticalParams(criticalParameters.keySet())
.customParams(criticalParameters)
.build();
//simplyfy your payload json string before..remove all spaces.
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
JsonElement el = JsonParser.parseString(payload);
String simplePayload=gson.toJson(el);
// With encoding the payload
Payload detachedPayload =new Payload(new Base64URL(simplePayload).toString());
JWSObject jwsObject = new JWSObject(header, detachedPayload );
jwsObject.sign(signer);
String jws = jwsObject.serialize(true);
JWSObject parsedJWSObject = JWSObject.parse(jws, detachedPayload );
What I try to achieve:
iOS client sends a JWT token to the backend.
Backend (Java) calls https://appleid.apple.com/auth/token to verify the token.
what I have so far:
to make Apple verification call:
restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("client_id", clientId); // app_id like com.app.id
String token = generateJWT(); // generated jwt
map.add("client_secret", token);
map.add("grant_type", "authorization_code");
map.add("code", authorizationCode); // JWT code we got from iOS
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
final String appleAuthURL = "https://appleid.apple.com/auth/token";
String response = restTemplate.postForObject(appleAuthURL, request, String.class);
token generation:
final PrivateKey privateKey = getPrivateKey();
final int expiration = 1000 * 60 * 5;
String token = Jwts.builder()
.setHeaderParam(JwsHeader.KEY_ID, keyId) // key id I got from Apple
.setIssuer(teamId)
.setAudience("https://appleid.apple.com")
.setSubject(clientId) // app id com.app.id
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.setIssuedAt(new Date(System.currentTimeMillis()))
.signWith(SignatureAlgorithm.ES256, privateKey) // ECDSA using P-256 and SHA-256
.compact();
return token;
to get my private key from the file:
final Reader pemReader = new StringReader(getKeyData());
final PEMParser pemParser = new PEMParser(pemReader);
final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
final PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
final PrivateKey pKey = converter.getPrivateKey(object);
I confirmed my JWT has all required fields:
{
"kid": "SAME KEY AS MY KEY ID",
"alg": "ES256"
}
{
"iss": "Blahblah",
"aud": "https://appleid.apple.com",
"sub": "com.app.id",
"exp": 1578513833,
"iat": 1578513533
}
This line caught my attention:
map.add("code", authorizationCode); // JWT code we got from iOS
The authorizationCode is not a jwt
JSON Web Tokens consist of 3 parts separated by dots
but the authorizationCode has 4 parts like this:
text1.text2.0.text3
You are probably using the identityToken from the iOS app instead of the authorizationCode
This is how you retrieve it:
let authorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8)!
print("authorizationCode: \(authorizationCode)")
Also good to have the following in mind for those who might come here after getting the same invalid_client error:
kid is the id for the private key from developer.apple.com/account/resources/authkeys/list
keyFile is the file holding the private key downloaded from developer.apple.com
teamID can be found by logging in to developer.apple.com and clicking on account, the teamID can be seen in the upper right corner
the value in aud should be https://appleid.apple.com
app_id is the bundle identifier for the app
In case it might help, here is a working solution in python to create a client_secret:
# $ pip install pyjwt
import jwt
import time
kid = "myKeyId"
keyFile = "/pathToFile/AuthKey.p8"
key = ""
with open(keyFile, 'r') as myFile:
key = myFile.read()
print(key)
timeNow = int(round(time.time()))
time3Months = timeNow + 86400*90
claims = {
'iss': teamID,
'iat': timeNow,
'exp': time3Months,
'aud': 'https://appleid.apple.com',
'sub': app_id,
}
secret = jwt.encode(claims, key, algorithm='ES256', headers={'kid': kid})
print("secret:")
print(secret)
client_secret = secret.decode("utf-8")
print(client_secret)
Save the clientSecret and appleToken into the local DB at login time with Apple ID.
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
print("didCompleteWithAuthorization : -\(authorization)")
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
// Create an account in your system.
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName?.givenName
let email = appleIDCredential.email
guard let appleIDToken = appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
return
}
StorageServices.storeInDefaults(object: idTokenString, key: "appleToken")
// Add new code below
if let authorizationCode = appleIDCredential.authorizationCode,
let codeString = String(data: authorizationCode, encoding: .utf8) {
StorageServices.storeInDefaults(object: codeString, key: "clientSecret")
}
default:
break
}
call the apple token revoke api.
func callRevokeTokenAPI() {
guard let clientSecret = StorageServices.readFromDefaults(key: "clientSecret") as? String else {return}
guard let appleToken = StorageServices.readFromDefaults(key: "appleToken") as? String else {return}
let parameters = "client_id=com.oxstren.Actofit-Wear&client_secret=\(clientSecret)&token=\(appleToken)&token_type_hint=access_token"
print(parameters)
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://appleid.apple.com/auth/revoke")!,timeoutInterval: Double.infinity)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let response = response as? HTTPURLResponse, error == nil else {
print("error", error ?? URLError(.badServerResponse))
return
}
print(response)
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
} //end function body.
I have implemented back-end which receives Google Sign In token from Android or iOS device and tries to verify it. Code has worked properly few months ago, it has not changed but recently it began to reject all tokens as invalid. When I try to repeat verification again with previously failed token after ~10s of its issue time, it begins to work and returns user information. Why does it happen?
try {
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory()).setIssuer(ISSUER).build();
GoogleIdToken idToken = verifier.verify(token);
if (idToken != null) {
Payload payload = idToken.getPayload();
ExternalUserInfo externalUserInfo = new ExternalUserInfo();
externalUserInfo.setId((String) payload.getSubject());
externalUserInfo.setName((String) payload.get("given_name"));
externalUserInfo.setFamilyName((String) payload.get("family_name"));
externalUserInfo.setEmail(payload.getEmail());
externalUserInfo.setLocale((String) payload.get("locale"));
externalUserInfo.setSystemId(AuthorizationMapper.TYPE_GOOGLE);
return externalUserInfo;
} else {
logger.debug("Invalid Google Sign in token " + token);
}
} catch (Exception e) {
logger.error("Error while getting Google Sign in user info for token " + token, e);
}
I'm using Play framework to develop consumer for Instagram real-time API. But still could not perform x-hub-signature verification properly. So, how can we perform Instagram x-hub-signature verification using Java and Play framework?
Here is my current code:
From the Play framework, I obtain the JSON payload using this method:
public static Result receiveInstaData(){
JsonNode json = request().body().asJson();
//obtain the x-hub-signature from the header
//obtain the corresponding client secret
VerificationResult verificationResult =
SubscriptionUtil.verifySubscriptionPostSignature(
clientSecret, json.toString(), xHubSignature);
if(verificationResult.isSuccess()){
//do something
}
}
Then inside the SubscriptionUtil, I perform verification using this following code:
public static VerificationResult verifySubscriptionPostSignature(String clientSecret, String rawJsonData, String xHubSignature) {
SecretKeySpec keySpec;
keySpec = new SecretKeySpec(clientSecret.getBytes("UTF-8"), HMAC_SHA1);
Mac mac;
mac = Mac.getInstance(HMAC_SHA1);
mac.init(keySpec);
byte[] result;
result = mac.doFinal(rawJsonData.getBytes("UTF-8"));
String encodedResult = Hex.encodeHexString(result);
return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
}
I created a standalone Python script that copies the instagram-python implementation and both of them produce the same results for the same clientSecret and jsonString. Maybe I should provide with raw binary data instead of String.
If let's say we need a raw binary data for JSON request, then I need to create my custom BodyParser to parse the JSON request to raw binary data[5]
References:
[1-4]http://pastebin.com/g4uuDwzn (SO doesn't allow me to post more than 2 links, so I put all the references here. The links contain the signature verification in Ruby, Python and PHP)
[5]https://groups.google.com/forum/#!msg/play-framework/YMQb6yeDH5o/jU8FD--yVPYJ
[6]My standalone python script:
#! /usr/bin/env python
import sys
import hmac
import hashlib
hc_client_secret = "myclientsecret"
hc_raw_response = "[{\"subscription_id\":\"1\",\"object\":\"user\",\"object_id\":\"1234\",\"changed_aspect\":\"media\",\"time\":1297286541},{\"subscription_id\":\"2\",\"object\":\"tag\",\"object_id\":\"nofilter\",\"changed_aspect\":\"media\",\"time\":1297286541}]"
client_secret = hc_client_secret
raw_response = hc_raw_response
if len(sys.argv) != 3:
print 'Usage verify_signature <client_secret> <raw_response>.\nSince the inputs are invalid, use the hardcoded value instead!'
else:
client_secret = sys.argv[1]
raw_response = sys.argv[2]
print "client_secret = " + client_secret
print "raw_response = " + raw_response
digest = hmac.new(client_secret.encode('utf-8'), msg=raw_response.encode('utf-8'), digestmod=hashlib.sha1).hexdigest()
print digest
Finally I managed to find the solution. For the Controller in Play Framework, we need to use BodyParser.Raw so the we can extract the payload request as raw data, i.e. array of bytes.
Here's the code for the controller in Play Framework:
#BodyParser.Of(BodyParser.Raw.class)
public static Result receiveRawInstaData(){
Map<String, String[]> headers = request().headers();
RawBuffer jsonRaw = request().body().asRaw();
if(jsonRaw == null){
logger.warn("jsonRaw is null. Something is wrong with the payload");
return badRequest("Expecting serializable raw data");
}
String[] xHubSignature = headers.get(InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
if(xHubSignature == null){
logger.error("Invalid POST. It does not contain {} in its header", InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
return badRequest("You are not Instagram!\n");
}
String json;
byte[] jsonRawBytes;
jsonRawBytes = jsonRaw.asBytes();
json = new String(jsonRawBytes, StandardCharsets.UTF_8);
try {
String clientSecret = InstaSubscriptionUtils.getClientSecret(1);
VerificationResult verificationResult = SubscriptionUtil.verifySubscriptionPostRequestSignature
(clientSecret,jsonRawBytes, xHubSignature[0]);
if(verificationResult.isSuccess()){
logger.debug("Signature matches!. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
}else{
logger.error("Signature doesn't match. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
return badRequest("Signature does not match!\n");
}
} catch (InstagramException e) {
logger.error("Instagram exception.", e);
return internalServerError("Internal server error. We will attend to this problem ASAP!");
}
logger.debug("Received xHubSignature: {}", xHubSignature[0]);
logger.info("Sucessfully received json data: {}", json);
return ok("OK!");
}
And for the code for method verifySubscriptionPostRequestSignature in SubscriptionUtil
public static VerificationResult verifySubscriptionPostRequestSignature(String clientSecret, byte[] rawJsonData, String xHubSignature) throws InstagramException{
SecretKeySpec keySpec;
keySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1);
Mac mac;
try {
mac = Mac.getInstance(HMAC_SHA1);
mac.init(keySpec);
byte[] result = mac.doFinal(rawJsonData);
String encodedResult = Hex.encodeHexString(result);
return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
} catch (NoSuchAlgorithmException e) {
throw new InstagramException("Invalid algorithm name!", e);
} catch (InvalidKeyException e){
throw new InstagramException("Invalid key: " + clientSecret, e);
}
}
I implemented this solution in jInstagram, here is the link to the source code: SubscriptionUtil