JWT signature different from expected [duplicate] - jwt

I wrote a method that takes a JWT as a request and checks if the signature is valid.
This is the unit test:
#Test
public void isValid() {
final JwtValidator jwtValidator = JwtValidator.getInstance();
final boolean valid = jwtValidator.isValid("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
Assert.isTrue(valid);
}
and this is the code:
#SneakyThrows
public boolean isValid(String extractedToken) {
final String[] tokenParts = extractedToken.split(Pattern.quote("."));
String header = tokenParts[0];
String payload = tokenParts[1];
String signature = tokenParts[2];
final byte[] calcHmacSha256 = HMAC.calcHmacSha256("your-256-bit-secret".getBytes(), (header+"."+payload).getBytes());
final String s = Base64.getEncoder().encodeToString(calcHmacSha256);
System.out.println("'" + signature + "'.equals('"+s+"')");
return signature.equals(s);
}
The log prints two strings that differ only for 2 chars, so I feel like I'm close "but not quite" to make it work:
'SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.equals('SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV/adQssw5c=')
There are of course hard coded values because the implementation isn't complete, but I'm using the example values in https://jwt.io/ for ease of use right now.
Thanks!
EDIT 1:
public class JwtValidatorTest {
#Test
public void isValid() {
byte[] header64 = Base64.getEncoder().encode("{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes());
byte[] payload64 = Base64.getEncoder().encode("{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}".getBytes());
final byte[] calcHmacSha256 = HMAC.calcHmacSha256("your-256-bit-secret".getBytes(), (header64+"."+payload64).getBytes());
final String signature64 = Base64.getEncoder().encodeToString(calcHmacSha256);
final String input = header64 + "." + payload64 + "." + signature64;
final JwtValidator jwtValidator = JwtValidator.getInstance();
final boolean valid = jwtValidator.isValid(input);
Assert.isTrue(valid);
}
}

The difference is just caused by the different encoding used here. You used Base64 encoding, but the original signature is Base64Url encoded. Base64Url encoding is, according to RFC7519, the standard encoding for JWT:
Each part contains a base64url-encoded value
Base64Url encoding has no padding (=) on the end and the characters + and / are replaced with -and _.
This code should solve the problem:
final String s = Base64.getUrlEncoder().withoutPadding().encodeToString(calcHmacSha256);

Related

Send an encrypted request to APi in Dart

I am trying to send an encrypted request to a specific API in dart, but without success - I don't have any experience with the Dart language.
This are the requirements:
The JSON to be sent is encrypted as follows: "AES/CBC/ZeroBytePadding", IV is generated according to SHA1PRNG with a length of 16 bytes.
The encrypted bytes are Base64 encoded. This results in the encryptedJson.
The hmac is generated from base64 encoded IV and the encryptedJson with "HmacSHA256".
A json will be generated: {"value":encryptedJson,"iv":initialisationVector,"mac":hmac}
This json will be base64 encoded and sent as an encrypted payload.
Can anyone help me? Thanks in advance!
This is the Dart Code so far.
import 'dart:convert';
import 'dart:core';
import 'package:crypto/crypto.dart' as crypto;
import 'package:encrypt/encrypt.dart' as enc;
String encrypt(String string) {
// json encryption
final enc.Key key = enc.Key.fromUtf8(env.get('password'));
final enc.IV iv = enc.IV.fromSecureRandom(IV_LENGTH);
final enc.Encrypter encrypter = enc.Encrypter(enc.AES(key, mode: enc.AESMode.cbc));
final encryptedJson = encrypter.encrypt(string, iv: iv);
final String IVBase64String = base64.encode(iv.bytes);
print('encrypted JSON: '+encryptedJson.base64);
print('decrypted JSON: '+encrypter.decrypt(encryptedJson, iv: iv));
crypto.Hmac hmacSha256 = new crypto.Hmac(crypto.sha256, key.bytes);
crypto.Digest sha256Result = hmacSha256.convert(iv.bytes + encryptedJson.bytes);
print('data: ' + encryptedJson.base64);
print('iv: ' + IVBase64String);
print('hmac: ' + sha256Result.toString());
// Payload
final encryptedText = "{\"value\":\""+encryptedJson.base64+"\",\"iv\":\""+IVBase64String+"\",\"mac\":\""+sha256Result.toString()+"\"}";
print('final: ' + jsonEncode(encryptedText));
return base64.encode(utf8.encode(encryptedText));
}
This is the JavaExample
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class ApiJavaSample
{
private final Cipher cipher;
private final SecretKeySpec key;
private static final String TAG = "AESCrypt";
private static final int IV_LENGTH = 16;
private String cypher_mode = "AES/CBC/NoPadding";
private String cypher_mode_iv = "SHA1PRNG";
public static void main (String[] args)
{
try{
System.out.println("encrypting");
ApiJavaSample test = new ApiJavaSample("password");
String encryptedString = test.encrypt("{\"coupon_key\":\"011205358365\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}");
System.out.println("encrpyted");
System.out.println(encryptedString);
}
catch(Exception e)
{
System.out.println(e);
}
}
public ApiJavaSample(String password) throws Exception
{
// hash password with SHA-256 and crop the output to 128-bit for key
//MessageDigest digest = MessageDigest.getInstance("SHA-256");
//digest.Updater(password.getBytes("UTF-8"));
byte[] keyBytes = password.getBytes();
cipher = Cipher.getInstance(cypher_mode);
key = new SecretKeySpec(keyBytes, "AES");
}
private String hmacDigest(String msg, String algo)
{
String digest = null;
try
{
//SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo);
Mac mac = Mac.getInstance(algo);
mac.init(key);
byte[] bytes = mac.doFinal(msg.getBytes("UTF-8"));
StringBuilder hash = new StringBuilder();
for (int i = 0; i < bytes.length; i++)
{
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1)
{
hash.append('0');
}
hash.append(hex);
}
digest = hash.toString();
}
catch (UnsupportedEncodingException | InvalidKeyException e)
{
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return digest;
}
public String encrypt(String plainText) throws Exception
{
byte[] iv_bytes = generateIv();
AlgorithmParameterSpec spec = new IvParameterSpec(iv_bytes);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
int blockSize = cipher.getBlockSize();
while (plainText.length() % blockSize != 0) {
plainText += "\0";
}
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedText = Base64.getEncoder().encodeToString(encrypted);
String iv_base64_string = Base64.getEncoder().encodeToString(iv_bytes);
String mac = hmacDigest(iv_base64_string + encryptedText.trim(), "HmacSHA256");
//JSONObject encryptedJson = new JSONObject();
//encryptedJson.put("value", encryptedText.trim());
//encryptedJson.put("iv", iv_base64_string);
//encryptedJson.put("mac", mac);
String base64Encrypt = "{\"value\":\""+encryptedText.trim()+"\",\"iv\":\""+iv_base64_string+"\",\"mac\":\""+mac+"\"}";
return Base64.getEncoder().encodeToString(base64Encrypt.getBytes());
}
private byte[] generateIv() throws NoSuchAlgorithmException
{
SecureRandom random = SecureRandom.getInstance(cypher_mode_iv);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
}
Here is my test data:
Plaintext:
"{\"coupon_key\":\"382236526272\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}"
Key:
33a485cb146e1153c69b588c671ab474
The following has to be changed/optimized in the Dart code:
The Java code uses Zero padding. PointyCastle and the encrypt package (a PointyCastle wrapper) do not support Zero padding (to my knowledge). A possible approach for the Dart code is to disable the default PKCS#7 padding in combination with a custom implementation for Zero padding.
The Java code applies the Base64 encoded data for the HMAC, while the Dart code uses the raw data. This has to be changed.
The Base64 encoding of the IV is obtained more efficiently with iv.base64.
Thus, the code is to be changed as follows:
import 'package:crypto/crypto.dart' as crypto;
import 'package:encrypt/encrypt.dart' as enc;
import 'package:convert/convert.dart';
import 'dart:typed_data';
import 'dart:convert';
String encrypt(String string) {
final enc.Key key = enc.Key.fromUtf8(env.get('password')); // Valid AES key
final enc.IV iv = enc.IV.fromSecureRandom(IV_LENGTH); // IV_LENGTH = 16
final dataPadded = pad(Uint8List.fromList(utf8.encode(string)), 16);
final enc.Encrypter encrypter = enc.Encrypter(enc.AES(key, mode: enc.AESMode.cbc, padding: null));
final encryptedJson = encrypter.encryptBytes(dataPadded, iv: iv);
crypto.Hmac hmacSha256 = crypto.Hmac(crypto.sha256, key.bytes);
crypto.Digest sha256Result = hmacSha256.convert(utf8.encode(iv.base64 + encryptedJson.base64));
final encryptedText = "{\"value\":\""+encryptedJson.base64+"\",\"iv\":\""+iv.base64+"\",\"mac\":\""+sha256Result.toString()+"\"}";
return base64.encode(utf8.encode(encryptedText));
}
Uint8List pad(Uint8List plaintext, int blockSize){
int padLength = (blockSize - (plaintext.lengthInBytes % blockSize)) % blockSize;
if (padLength != 0) {
BytesBuilder bb = BytesBuilder();
Uint8List padding = Uint8List(padLength);
bb.add(plaintext);
bb.add(padding);
return bb.toBytes();
}
else {
return plaintext;
}
}
Test (using a static IV to allow comparison between the ciphertexts of the two codes):
Key: enc.Key.fromUtf8("5432109876543210")
IV: enc.IV.fromUtf8("0123456789012345")
Plaintext: "{\"coupon_key\":\"011205358365\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}"
Result: eyJ2YWx1ZSI6InNRTjJ0OWc5ZWY2RzdNV2RsOFB3emlXSlQwclNxUWJ2ZnN0eCtpMmNtSTQyUXJjUGRNV0JLbTlRZ2kxdmM0dElna2NOZEJsOVpEM0JlYTFPZ1kxaHNSeklSbHM1TnlaN0s1T2NqMTEzdkdvPSIsIml2IjoiTURFeU16UTFOamM0T1RBeE1qTTBOUT09IiwibWFjIjoiMzkwYzlhMzAxMjAxYjc1MWUxNjBhM2JlZTdmZGU5YzE5ZDY0MzJlNTBjOTJhNTg0ODBhMTJkNTYyNWRkYWMyNSJ9
After the changes, both codes return the above result for the above input data.
Security:
Typically, an AES key is a randomly generated byte sequence and not a string. If the key is to be derived from a passphrase/string, a reliable key derivation like PBKDF2 is to be used.
Zero padding is unreliable, so the reliable PKCS#7 padding that most libraries use by default should be applied. If the Java code had used PKCS#7 padding, porting would have been easier.
For encoding/decoding the charset should be specified (e.g. getBytes(StandardCharsets.UTF_8)), otherwise the default encoding will be used (which might not be wanted).
Using the same key for encryption and integrity checking for AES/HMAC is not a pressing security issue, but should be avoided as a preventive measure, see here.
The code is partially inefficient, e.g. when concatenating the Base64 encoded data instead of the raw data to determine the HMAC.

Zephir Returned values by functions can only be assigned to variant variables

So, i want to make an PHP extension by Zephir that can encrypt and encode a source code, but i have an error which is :
Error Message:
[ERROR] Returned values by functions can only be assigned to variant variables in
/home/ubuntu/ta/utils/utils/Cryptix.zep on line 15
Script zephir handling the __exec_command event returned with error code 1
Here is the Code:
Cryptix.zep
namespace Utils;
class Cryptix
{
/**
*
*
* #param string type
* #param string file
*
*/
public function encryptFile(type, file)
{
string code = file_get_contents(file, true);
string enc_code = "<?php Utils::decode('"+type+"', '"+openssl_enc(type, code)+"'); ?>";
file_put_contents(file + ".original", code);
file_put_contents(file, enc_code);
}
public function openssl_enc(string method, string data)
{
string firstkey = "Lk5Uz3slx3BrAghS1aaW5AYgWZRV0tIX5eI0yPchFz4=";
string secondkey = "EZ44mFi3TlAey1b2w4Y7lVDuqO+SRxGXsa7nctnr/JmMrA2vN6EJhrvdVZbxaQs5jpSe34X3ejFK/o9+Y5c83w==";
string first_key = base64_decode(firstkey);
string second_key = base64_decode(secondkey);
string iv_length = openssl_cipher_iv_length(method);
string iv = openssl_random_pseudo_bytes(iv_length);
string first_encrypted = openssl_encrypt(data, method, first_key, OPENSSL_RAW_DATA, iv);
string second_encrypted = hash_hmac("sha3-512", first_encrypted, second_key, true);
string output = base64_encode(iv + second_encrypted + first_encrypted);
return output;
}
}
what should i do to fix this error? thank you so much for your help.

Creating a Signed URL for Google Cloud Storage

We have an ERP application running on GCP .
For downloading data spanning more than three months or so ,we're uploading a file on GCS. Now i want to create a signed url so that to give limited access to the end users .
I have been trying this. But i get this error :
Signature does not match. Please check your Google secret key.
Can anyone tell how to go about this?
private static final int EXPIRATION_TIME = 5;
private static final String BASE_URL = "https://storage.googleapis.com";
private static final String httpVerb = "GET";
/*
* private static final String BUCKET = "my_bucket"; private static final String
* FOLDER = "folder";
*/
private final AppIdentityService identityService = AppIdentityServiceFactory.getAppIdentityService();
public String getSignedUrl(String bucket, final String fileName, String contentTpe) throws Exception {
final long expiration = expiration();
final String unsigned = stringToSign(bucket, expiration, fileName, contentTpe);
final String signature = sign(unsigned);
return new StringBuilder(BASE_URL).append("/").append(bucket).append("/").append(fileName)
.append("?GoogleAccessId=").append(clientId()).append("&Expires=").append(expiration)
.append("&Signature=").append(URLEncoder.encode(signature, "UTF-8")).toString();
}
private static long expiration() {
final long unitMil = 1000l;
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, EXPIRATION_TIME);
final long expiration = calendar.getTimeInMillis() / unitMil;
return expiration;
}
private String stringToSign(String bucket, final long expiration, String filename, String contentType) {
final String contentMD5 = "";
final String canonicalizedExtensionHeaders = "";
final String canonicalizedResource = "/" + bucket + "/" + filename;
final String stringToSign = httpVerb + "\n"+ contentMD5 + "\n" + contentType + "\n" + expiration + "\n"
+ canonicalizedExtensionHeaders + canonicalizedResource;
return stringToSign;
}
protected String sign(final String stringToSign) throws UnsupportedEncodingException {
final SigningResult signingResult = identityService.signForApp(stringToSign.getBytes());
final String encodedSignature = new String(Base64.encodeBase64(signingResult.getSignature()), "UTF-8");
return encodedSignature;
}
protected String clientId() {
return identityService.getServiceAccountName();
}
URL signing code is a bit tricky because by its nature it can be difficult to know what you've gotten wrong, other than just seeing that it's wrong. There are a few general tips that make it easier:
First, if possible, consider using URL signing functions in the google-cloud libraries. For example, the Java google-cloud library provides a Storage.signURL method, and you can use it like this:
URL signedUrl = storage.signUrl(
BlobInfo.newBuilder(bucketName, blobName).build(),
2, TimeUnit.DAYS);
Second, if you look at the error message, you'll notice that there's a <StringToSign> section. This section contains the exact string that GCS would calculate a signature for. Make sure that the string you're signing matches this string exactly. If it doesn't, that's your problem.
In your code's particular case, I didn't find the problem, but it might be that you're including a content-type line when signing the string, but GET requests don't provide a Content-Type header. It's just an idea, though, since I don't see your invocation of getSignedUrl.

Swift Biginteger xor encrypt/decrypt

I'm new to Swift, now I just want to translate java xor encrypt/decrypt code to Swift, which is used for transactions between server and client. Below is the Java xor code:
public static String encrypt(String password, String key) {
if (password == null)
return "";
if (password.length() == 0)
return "";
BigInteger bi_passwd = new BigInteger(password.getBytes());
BigInteger bi_r0 = new BigInteger(key);
BigInteger bi_r1 = bi_r0.xor(bi_passwd);
return bi_r1.toString(16);
}
public static String decrypt(String encrypted, String key) {
if (encrypted == null)
return "";
if (encrypted.length() == 0)
return "";
BigInteger bi_confuse = new BigInteger(key);
try {
BigInteger bi_r1 = new BigInteger(encrypted, 16);
BigInteger bi_r0 = bi_r1.xor(bi_confuse);
return new String(bi_r0.toByteArray());
} catch (Exception e) {
return "";
}
}
And I've searched a lot about swift xor encryption and tried the answer in the links below:
XOR Encryption in Swift IOS
https://coderwall.com/p/timkvw/simple-xor-encryption-and-decryption-in-swift-playground-code
Swift Simple XOR Encryption
But all of them can't get the same encryted string compared with my java code, now my java code was live, and there's no way to change it.
I thought it might caused by the hexadecimal, but in swift, I can't find anywhere about swift xor hexadecimal.
So what I need is the swift code can get the exact same encrypted string as java code I've pasted, the encrypted string generated by my java client can be decrypted in my iOS client.
Thanks very much whoever can help me out of this! I've get into this a whole day!
Thanks again.
Solved, below is the code.
Based on the great work at https://github.com/lorentey/BigInt
func encrypt(str:String, key:BigUInt)->String
{
let value = BigUInt(str.data(using: String.Encoding.utf8)!)
let encrypt = key ^ value
return String(encrypt, radix: 16)
}
func decrypt(str:String, key:BigUInt)->String
{
let value = BigUInt(str, radix: 16)!
let decrypt = key ^ value
return String(data: decrypt.serialize(), encoding: String.Encoding.utf8)!
}
Thanks again for everyone whoever contributed to the the BigInt library, that's why the code looks so simple.

Malformed HTTP post using WWWForm

I'm using UnityHTTP (https://github.com/andyburke/UnityHTTP) to call a REST API ( KiiCloud http://www.kii.com ) and it works great but I want to get rid of the 3rd party library if possible and use Unity's WWW and WWWForm to achieve the same.
Here's the code that uses UnityHTTP that works fine:
public static void RunServerExtension (string appId, string appKey, string endpoint, string kii_access_token, string msg)
{
Hashtable data = new Hashtable();
// Add json fields with values here (use as dictionary)
data.Add("message", msg);
// When you pass a Hashtable as the third argument, we assume you want it send as JSON-encoded
// data. We'll encode it to JSON for you and set the Content-Type header to application/json
HTTP.Request myRequest = new HTTP.Request( "post", "https://api.kii.com/api/apps/" + appId + "/server-code/versions/current/" + endpoint, data);
myRequest.AddHeader("x-kii-appid", appId);
myRequest.AddHeader("x-kii-appkey", appKey);
if(kii_access_token != null)
theRequest.AddHeader("Authorization", "Bearer " + kii_access_token);
myRequest.Send( ( request ) => {
// we provide Object and Array convenience methods that attempt to parse the response as JSON
// if the response cannot be parsed, we will return null
// note that if you want to send json that isn't either an object ({...}) or an array ([...])
// that you should use JSON.JsonDecode directly on the response.Text, Object and Array are
// only provided for convenience
Hashtable result = request.response.Object;
if ( result == null )
{
Debug.LogWarning( "Could not parse JSON response!" );
return;
}
Debug.Log ("Got response");
Debug.Log(request.response.Text);
});
}
So the above works just fine but when I switch to WWWForm in this way:
public static WWW RunServerExtension (string appId, string appKey, string endpoint, string kii_access_token, string msg)
{
WWWForm form = new WWWForm();
Hashtable headers = form.headers;
headers["Content-Type"] = "application/json";
headers["x-kii-appid"] = appId;
headers["x-kii-appkey"] = appKey;
if(kii_access_token != null)
headers["Authorization"] = "Bearer " + kii_access_token;
form.AddField("message", msg);
return new WWW("https://api.kii.com/api/apps/" + appId + "/server-code/versions/current/" + endpoint, form.data, headers);
}
private IEnumerator WaitForRequest(WWW www)
{
yield return www;
// check for errors
if (www.error == null)
{
Debug.Log("WWW Ok!: " + www.text);
} else {
Debug.Log("WWW Error: "+ www.error);
}
}
I get a BAD REQUEST on the server side (meaning the request is malformed, not what the server was expecting). Note that the headers must be passed as parameter otherwise the server complains about missing headers.
I suspected this might be related to the fact that the server expects JSON data so I converted the message to JSON using UnityHTTP JSON class (you can use just that isolated class for JSON encoding/decoding) https://github.com/andyburke/UnityHTTP/blob/master/external/JSON.cs so this method passes {"message":"This is echoed!!"} as data:
public static WWW RunServerExtension (string appId, string appKey, string endpoint, string kii_access_token, string msg)
{
WWWForm form = new WWWForm();
Hashtable headers = form.headers;
headers["Content-Type"] = "application/json";
headers["x-kii-appid"] = appId;
headers["x-kii-appkey"] = appKey;
if(kii_access_token != null)
headers["Authorization"] = "Bearer " + kii_access_token;
Hashtable data = new Hashtable();
data["message"] = msg;
byte[] bytes = GetBytes(JSON.JsonEncode(data));
return new WWW("https://api.kii.com/api/apps/" + appId + "/server-code/versions/current/" + endpoint, bytes, headers);
}
static byte[] GetBytes(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
But still the same BAD REQUEST. Do you see why this could be failing? Why UnityHTTP works?
As I mentioned in comments: C# converts all strings to UTF-16. If your webserver is expecting a different encoding, simply passing the bytes verbatim will not produce good results.
JSON is typically encoded in UTF-8, but it's best if an API specifies its input/output encodings explicitly.
I took a bit more time, today. If you check UnityHTTP's source, you can see that their Hashtable constructor encodes JSON in UTF-8:
this.bytes = Encoding.UTF8.GetBytes( JSON.JsonEncode( data ) );
Your code does not change the string's encoding, which means you're sending the wrong bytes.