I'm using bcryptjs to hash a user's refresh_token before storing it in my database.
It seems that the following always evaluates to true when comparing a hashed string with a JWT, I've also gotten the same behavior on https://bcrypt-generator.com/
for example the hash $2a$10$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW is a match with the following two JWTs
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY
You can verify these on the site as well that they both result in a 'match'
Go to https://bcrypt-generator.com/ and open your browser console.
Enter these lines into the console:
> var jwt1 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU"
< undefined
> var jwt2 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY"
< undefined
> var h = "$2a$10$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW"
< undefined
Then enter these lines into the console, observe how they return true:
> bcrypt.compareSync(jwt1, h)
< true
> bcrypt.compareSync(jwt2, h)
< true
This is my own JS code that also reproduces the hash match:
// Login Logic
const refresh_token: string = jwt.sign({ userId }, authSecrets.refresh_secret, { expiresIn: '30d' });
const hash_refresh = bcrypt.hashSync(refresh_token);
await UserModel.update({
id: user.id,
refresh_token: hash_refresh,
});
// Refresh logic
// 'value' is the payload after using joi to validate it
const claims: any = jwt.verify(value.refresh_token, authSecrets.refresh_secret);
user = await UserModel.get(claims.userId);
if (!bcrypt.compareSync(value.refresh_token, user.refresh_token)) {
// This never happens with any JWT!
return response(401, 'Refresh Token is incorrect');
}
Why is this happening? the strings are clearly different (although not by a lot).
The hash collisions are because bcrypt only hashes the first 72 bytes of input (in most implementations).
This is documented in the README for both the bcryptjs and bcrypt npm packages:
bcryptjs:
The maximum input length is 72 bytes (note that UTF8 encoded characters use up to 4 bytes) and the length of generated hashes is 60 characters.
bcrypt:
Per bcrypt implementation, only the first 72 bytes of a string are used. Any extra bytes are ignored when matching passwords. Note that this is not the first 72 characters. It is possible for a string to contain less than 72 characters, while taking up more than 72 bytes (e.g. a UTF-8 encoded string containing emojis).
(That's an objectively terrible design considering this is for user-security... The bcryptjs library really should always throw an exception if the input exceeds 72 bytes IMO)
I note that bcrypt is design for human-supplied (i.e. non-random) passwords, not as a general-purpose message-digest algorithm. Given you don't need to add a salt to randomly-generated passwords (like your refresh_token value) you probably should use something like a SHA-2 family algorithm (e.g. SHA-256, but not SHA-1) for this.
Related
I am trying to use CryptoJS to encrypt something and then generate a hexadecimal string of the encrypted text.
function EncryptAES(text, key) {
var encrypted = CryptoJS.AES.encrypt(text, key);
return CryptoJS.enc.Hex.stringify(encrypted);
}
var encrypted = EncryptAES("Hello, World!", "SuperSecretPassword");
console.log(encrypted);
However, instead of a hexadecimal string, a blank line is printed to the console. What am I doing wrong?
CryptoJS.AES.encrypt() returns a CipherParams object that encapsulates several data, including the ciphertext as WordArray (s. here). By default, .toString() returns the hex encoded data for a WordArray:
function EncryptAES(text, key) {
var encrypted = CryptoJS.AES.encrypt(text, key);
return encrypted.ciphertext.toString()
}
var encrypted = EncryptAES("Hello, World!", "SuperSecretPassword");
console.log(encrypted);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Note that in your example the key material is passed as string and therefore interpreted as passphrase (s. here), inferring key and IV via a key derivation function in conjunction with a random 8 bytes salt, which is why the ciphertext changes each time for the same input data.
Therefore, decryption requires not only the ciphertext but also the salt, which is also encapsulated in the CipherParams object.
For a CipherParams object, .toString() returns the data in the Base64 encoded OpenSSL format consisting of the ASCII encoding of Salted__ followed by the 8 bytes salt and the actual ciphertext, and thus contains all the information needed for decryption.
I try to connect to Coinex API through a Java program
I follow exact patter that mentioned in below link for authorisation
https://github.com/coinexcom/coinex_exchange_api/wiki/012security_authorization
I MD5 has whole query string that is like below , and put result in authorization in request header parameter
tonce=1635504041595&access_id=XXXX&secret_key=YYYY
My intentions is to get account balance so my Get request URL is
https://api.coinex.com/v1//balance/info?tonce=1635504041595&access_id=XXXX
but server return below error
{"code": 25, "data": {}, "message": "Signature Incorrect"}
Anybody can advice what is the issue , thanks AndyJ
well this is my encode method:
public static String encode(String str) {
try {
// Generate a summary of MD5 encryption calculations
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes("UTF-8"));
// digest() finally determines to return the md5 hash value, returning a value of 8 as a string. Because the md5 hash value is a 16-bit hex value, it is actually an 8-bit character.
// The BigInteger function converts an 8-bit string into a 16-bit hex value, represented by a string; gets a hash value in the form of a string
String md5 = new BigInteger(1, md.digest()).toString(16);
//BigInteger will omit 0 and need to be completed to 32 bits
return fillMD5(md5).toUpperCase();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
and one more thing,
your timestmap should have 10 digits, remove the last 3 "000" like this:
Integer.parseInt(String.valueOf(timestamp).substring(0, 10));
I'm trying to add a Content-MD5 header to my REST calls to AWS S3 in Haxe (compiling to PHP). It's generated by
var contentMD5 = haxe.crypto.Base64.encode(haxe.io.Bytes.ofString(haxe.crypto.Md5.encode(_data)));
with _data in my example being
<Delete><Object><Key>nathan/storage/72ENgrtnpA5VAoy7zEpzPRNEChN0TRGc</Key></Object><Object><Key>nathan/storage/7rlZZSJFvZ7AxUhQZsh4ufn9M2x8m1ae</Key></Object><Object><Key>nathan/storage/HN8NFlUnJiiGo7qlddvRrlGE6hPmWMnZ</Key></Object><Object><Key>nathan/storage/SFsZ8z63DswEVFJQJqmUwbenaWyfZ8zb</Key></Object><Object><Key>nathan/storage/YSYXXgYbSZixOKo27PL65ii6nCeiFesl</Key></Object></Delete>
My full request sent to AWS (for a multiple delete call as described here http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html), using AWS signature version 4 (bucket, signature, and credential shortened):
POST /?delete= HTTP/1.1
Host: mybucket.s3-eu-central-1.amazonaws.com
Content-Length: 392
x-amz-content-sha256: 53da469cb6fc9d0701a1c6ff98d48edd361cd8a90d8a290a2dd224b2681bf7fb
x-amz-date: 20150923T195117Z
Authorization: AWS4-HMAC-SHA256 Credential=zzzzz/20150923/eu-central-1/s3/aws4_request, SignedHeaders=content-md5;host;x-amz-date, Signature=xxxxx
Content-MD5: MDAzNDZmZjJiMGJkMDFkNzVjYzFiOGE4MzI5NTc0NGY=
<Delete><Object><Key>nathan/storage/72ENgrtnpA5VAoy7zEpzPRNEChN0TRGc</Key></Object><Object><Key>nathan/storage/7rlZZSJFvZ7AxUhQZsh4ufn9M2x8m1ae</Key></Object><Object><Key>nathan/storage/HN8NFlUnJiiGo7qlddvRrlGE6hPmWMnZ</Key></Object><Object><Key>nathan/storage/SFsZ8z63DswEVFJQJqmUwbenaWyfZ8zb</Key></Object><Object><Key>nathan/storage/YSYXXgYbSZixOKo27PL65ii6nCeiFesl</Key></Object></Delete>
The response is as follows (RequestId and HostId shortened)
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidDigest</Code><Message>The Content-MD5 you specified was invalid.</Message><Content-MD5>MDAzNDZmZjJiMGJkMDFkNzVjYzFiOGE4MzI5NTc0NGY=</Content-MD5><RequestId>rrrrrr</RequestId><HostId>ccccccc</HostId></Error>
In my opinion, the generated MD5 is correct. I verified the value with other tools. Also, note that x-amz-content-sha256 is based on the same _data and AWS accepted that header in my previous (non delete) calls.
What am I missing here? Why does my MD5 value differ from the one AWS generates?
You're very close.
Here's the problem:
An md5 hash is 16 bytes in binary representation, 32 characters in hexadecimal representation, and 24 characters (Including padding) in base64.
Yours is approximately twice as long. You appear to be taking the 32 character hex md5, and base64-encoding that, resulting in a base64 string of about 44 characters, instead of just encoding the binary form.
Note that the length of the output, 44 vs 24 is not a factor of two in spite of my assertion that you are encoding 32 initial bytes instead of 16. That's expected, because base64 output_bytes = ceil(input_bytes/3) * 4.
My expertise is with the S3 API -- not haxe, which I've never used -- so the above is almost certainly correct, but the following is wild speculation.
var contentMD5 = haxe.crypto.Base64.encode(haxe.crypto.Md5.make(_data));
Any one out there looking to do this in java can use this code
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;
public class GenerateMD5 {
public static void main(String args[]) throws Exception{
String s = "<CORSConfiguration> <CORSRule> <AllowedOrigin>http://www.example.com</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> </CORSConfiguration>";
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(s.getBytes());
byte[] digest = md.digest();
StringBuffer sb = new StringBuffer();
/*for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}*/
System.out.println(sb.toString());
StringBuffer sbi = new StringBuffer();
byte [] bytes = Base64.encodeBase64(digest);
String finalString = new String(bytes);
System.out.println(finalString);
}
}
The commented code is where most people get it wrong changing it to hex
I have an email messge on an Exchange server (2010 SP1) with a Subject header that is 272 characters long. Both Outlook and OWA show it truncated to the first 252 characters followed by "...". EWSEditor shows it the same way. I know, however, that the full Subject is stored somewhere, because when I look at the headers in Message Options dialog Outlook or in the Message Details in OWA, all 272 characters are there.
My code is only gettting the truncated Subject, and I need a way to get the full string.
My code is using SyncFolderItems to get a ChangeCollection of ItemChange objects. I have two code branches for this. One retrieves FirstClassProperties, and one retrieves IdOnly. I have a function called getItemStringProp(), and depending on the branch, I either call it directly with the Item that I get from the ItemChange, or with the Item that I get by binding to the ItemChange.Item.Id. In both cases, my getItemStringProp() uses Item.TryGetProperty() and returns a max of 255 characters for the Subject. If the actual subject is longer, then I get 252 chars followed by "...".
Here's my code from the branch doing SyncFolderItems with FirstClassProperties:
useIdOnly = false;
icc = exchange.SyncFolderItems(folderId, PropertySet.FirstClassProperties, null, syncFolderItemsBatchSize, SyncFolderItemsScope.NormalItems, result.getSyncState());
and from the other branch:
useIdOnly = true;
icc = exchange.SyncFolderItems(folderId, PropertySet.IdOnly, null, syncFolderItemsBatchSize, SyncFolderItemsScope.NormalItems, result.getSyncState());
Following this, I drill down to get the Subject:
foreach (ItemChange ic in icc)
{
if (!useIdOnly)
{
icSubject = getItemStringProp(ic.Item, EmailMessageSchema.Subject,"Subject", folderName,"");
}
else
{
PropertySet itemProps = new PropertySet(BasePropertySet.IdOnly);
itemProps.Add(EmailMessageSchema.Subject);
itemProps.Add(EmailMessageSchema.DateTimeSent);
itemProps.Add(EmailMessageSchema.ItemClass);
Item item = Item.Bind(exchange, ic.Item.Id, itemProps);
icSubject = getItemStringProp(item, EmailMessageSchema.Subject, "Subject", folderName, "");
}
}
And here's the function that gets the Subject:
private String getItemStringProp(Item item, PropertyDefinition propDef, String propName, String fName, String defaultValue)
{
// some debug logging code and error checks omitted
object prop = null;
String value = "";
try
{
if (item.TryGetProperty(propDef, out prop) && prop != null)
{
value = prop.ToString();
}
if (prop == null || value == null)
{
value = defaultValue;
}
}
return value;
}
By the way, I'm aware that neither Outlook (at least the 2007 version) nor OWA allows creation of a message with a Subject longer than 255 characters. The message in question came into Exchange via SMTP, and a Subject far longer than 255 characters is legal according to the RFCs.
Don't rely on Item.Bind(), sync, search, or any other operation in EWS to load up all of the properties you're looking for. Have you tried getting the item, then doing a .load(PropertySet) or ExchangeService.loadPropertiesForItems()? Some properties won't come through in various retrieval actions even if you specifically request them. Some may come through, but get truncated. What makes it more fun is that I don't think there's any documentation telling you exactly which operations will return which properties, so you get to guess and check. You have to load the property set after you retrieve the Item(s), so it's usually best to get the Item with the ID only, then load the property set.
I made a test suite for math:hmac_* KRL functions. I compare the KRL results with Python results. KRL gives me different results.
code: https://gist.github.com/980788 results: http://ktest.heroku.com/a421x68
How can I get valid signatures from KRL? I'm assuming that they Python results are correct.
UPDATE: It works fine unless you want newline characters in the message. How do I sign a string that includes newline characters?
I suspect that your python SHA library returns a different encoding than is expected by the b64encode library. My library does both the SHA and base64 in one call so I to do some extra work to check the results.
As you show in your KRL, the correct syntax is:
math:hmac_sha1_base64(raw_string,key);
math:hmac_sha256_base64(raw_string,key);
These use the same libraries that I use for the Amazon module which is testing fine right now.
To test those routines specifically, I used the test vectors from the RFC (sha1, sha256). We don't support Hexadecimal natively, so I wasn't able to use all of the test vectors, but I was able to use a simple one:
HMAC SHA1
test_case = 2
key = "Jefe"
key_len = 4
data = "what do ya want for nothing?"
data_len = 28
digest = 0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79
HMAC SHA256
Key = 4a656665 ("Jefe")
Data = 7768617420646f2079612077616e7420666f72206e6f7468696e673f ("what do ya want for nothing?")
HMAC-SHA-256 = 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843
Here is my code:
global {
raw_string = "what do ya want for nothing?";
mkey = "Jefe";
}
rule first_rule {
select when pageview ".*" setting ()
pre {
hmac_sha1 = math:hmac_sha1_hex(raw_string,mkey);
hmac_sha1_64 = math:hmac_sha1_base64(raw_string,mkey);
bhs256c = math:hmac_sha256_hex(raw_string,mkey);
bhs256c64 = math:hmac_sha256_base64(raw_string,mkey);
}
{
notify("HMAC sha1", "#{hmac_sha1}") with sticky = true;
notify("hmac sha1 base 64", "#{hmac_sha1_64}") with sticky = true;
notify("hmac sha256", "#{bhs256c}") with sticky = true;
notify("hmac sha256 base 64", "#{bhs256c64}") with sticky = true;
}
}
var hmac_sha1 = 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79';
var hmac_sha1_64 = '7/zfauXrL6LSdBbV8YTfnCWafHk';
var bhs256c = '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843';
var bhs256c64 = 'W9zBRr9gdU5qBCQmCJV1x1oAPwidJzmDnexYuWTsOEM';
The HEX results for SHA1 and SHA256 match the test vectors of the simple case.
I tested the base64 results by decoding the HEX results and putting them through the base64 encoder here
My results were:
7/zfauXrL6LSdBbV8YTfnCWafHk=
W9zBRr9gdU5qBCQmCJV1x1oAPwidJzmDnexYuWTsOEM=
Which match my calculations for HMAC SHA1 base64 and HMAC SHA256 base64 respectively.
If you are still having problems, could you provide me the base64 and SHA results from python separately so I can identify the disconnect?