I'm trying to convert a hash function originally written in Java to Javascript in our codebase. However, I am getting different results.
Below is the code in Java
public static String hashText(String str) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA-512");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
messageDigest = null;
}
byte[] bytes = str.getBytes();
for (int i = 0; i < 64; i++) {
messageDigest.update(bytes);
bytes = messageDigest.digest();
}
hashedText = new String(Base64.encode(bytes, 2));
return hashedText.replace(StringUtils.LF, "");
}
And here is what I wrote in Javascript
function hashText(text){
const crypto = require('crypto')
const hash = crypto.createHash('sha512');
const digest = hash.update(text).digest();
return digest.toString("base64")
}
console.log(hashText(text))
I've been trying to figure out what I am doing wrong here but no success yet. I need help!
That for loop in the java, it looks like it's rehashing the hash 64 times. Looks like you're only hashing once in JavaScript. Here's an example of how you might do that loop in node.js.
function hashText(text, iter = 1) {
const crypto = require('crypto');
let digest = text;
for (let i = 0; i < iter; i++) {
const hash = crypto.createHash('sha512');
digest = hash.update(digest).digest();
}
return digest.toString('base64');
}
const text = 'asdf 1234 zxcv 5678';
console.log(hashText(text, 64));
*Edit: I don't use java, didn't know how MessageDigest.digest(), worked. Specifically:
... The digest is reset after this call is made.
In node, this means running createHash every iteration of the loop. Thanks #dave_thompson_085 for pointing this out!
It turns out the way nodejs crypto hash copy works is kinda different. As stated in the docs, it creates a new Hash object that contains a deep copy of the internal state of the current Hash object. However, what I need is to create a new hash with the encoded string.
Below is a sample of a working snippet
function hashText(text, length) {
const crypto = require('crypto');
let digest = text;
for (let i = 0; i < length; i++) {
const hash = crypto.createHash('sha512');
hash.update(digest);
digest = hash.digest();
}
return digest.toString('base64');
}
console.log(hashText(text, 64));
Thanks to everyone that helped one way or the other
Related
I have a simple hashing function in Java that I rewrote in Node.js but they produce different result.
Here is the Java:
public static String get_SHA_512_SecurePassword(String str, String customerId) {
try {
MessageDigest instance = MessageDigest.getInstance("SHA-512");
instance.update(customerId.getBytes(StandardCharsets.UTF_8));
byte[] digest = instance.digest(str.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(Integer.toString((b & 255) + 256, 16).substring(1));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
and here is the Node.js equivalent I produced.
let crypto = require('crypto');
function get_SHA_512_SecurePassword(str, customerId) {
let hash = crypto.createHash('sha512')
hash.update(customerId, 'utf8')
let value = hash.digest(str, 'uft8')
console.log(value.toString('hex'))
return value.toString('hex');
}
Can anyone explain what I am doing wrong or why they are different if I reproduced right?
You're very nearly there, the issue is that the .digest function does not take an argument in Node.js, so we'll call .update twice, once with customerId, then with str. We don't actually need to pass the string encoding to the .update function since utf8 is the default encoding.
const crypto = require('crypto');
function get_SHA_512_SecurePassword(str, customerId) {
const hash = crypto.createHash('sha512');
const digest = hash.update(customerId, "utf-8").update(str, "utf-8").digest();
return digest.toString("hex");
}
console.log(get_SHA_512_SecurePassword("hello", "world"));
This Node.js example outputs:
3e64afa1cb7d643aa36f63b8d092ad76b1f04ff557abbb3d05f5b9037abf68a6606a8885d51bec8f6f39ee7d0badd504241c3704e777a51c21a9723e285fb9b8
Which should be the same output as the Java code.
Your problem is that you use the crypto wrong in node.js. The digest() method in java is a little different than in crypto in node.js
Java MessageDigest Api Doc
digest(byte[] input) Performs a final update on the digest using the
specified array of bytes, then completes the digest computation.
So in java you provide another string in digest and consumes it to produce a new hash and then produces the output
In node.js however the documentation for hmac.digest() states Crypto Doc
hmac.digest([encoding])
Calculates the HMAC digest of all of the data passed using
hmac.update(). If encoding is provided a string is returned; otherwise
a Buffer is returned;
So it will not accept another string to be encoded like you pass str in that function. It will accept only an encoding.
So in your code
function get_SHA_512_SecurePassword(str, customerId) {
let hash = crypto.createHash('sha512')
hash.update(customerId, 'utf8')
let value = hash.digest(str, 'uft8') <-----this is wrong
console.log(value.toString('hex'))
return value.toString('hex');
}
The following would be the right one
function get_SHA_512_SecurePassword(str, customerId) {
let hash = crypto.createHash('sha512')
hash.update(customerId, 'utf8')
hash.update(str, 'utf8')
let value = hash.digest('hex')
console.log(value)
return value;
}
I've been working on a PHP script to work with a Java script, they both need to encrypt and decrypt using the same procedure/iv/key/method etc so that data passed both directions is translatable.
I believe I need to be using opennssl_encrypt, and openssl_decrypt and am avoiding mcrypt, and I'm most of the way there but something is going awry and I cannot figure out what is wrong.
Script basically takes the salts (x3) and runs a loop up to the number of salts, suffixing the input/looped value each time.
This is the Java encrypt:
VALUE = "myinputvalue"; // test value for string tests
ALGORITHM = "Blowfish";
MODE = "Blowfish/CBC/PKCS5Padding";
IV = "myivmyiv"; //random test value, 8 characters
KEY = "atextstringencryptionkey";
SALT = {"salt1111", "salt2222", "salt3333"};
ITERATIONS = SALT.length;
String encrypt(String value) {
try{
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(MODE);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(IV.getBytes()));
String valueToEncode = value;
for(int i = 0; i < ITERATIONS; i++){
valueToEncode = SALT[i] + valueToEncode;
byte[] values = cipher.doFinal(valueToEncode.getBytes());
valueToEncode = Base64.encodeToString(values, Base64.DEFAULT);
Log.i("Encryption " + (i + 1), valueToEncode);
}
return valueToEncode;
}
catch(Exception e){
e.printStackTrace();
return "";
}
}
This is my PHP script:
$valueToEncode = "myinputvalue";
$cipher_method = "BF-CBC";
$enc_iv = "myivmyiv";
$options = 0;
$enc_key = "atextstringencryptionkey";
$salts = array('salt1111', 'salt2222', 'salt3333');
$arrayLength = count($salts);
$iterations = array();
$i = 0;
while ($i < $arrayLength) {
$iterations[$i] = strlen($salts[$i]);
$i++;
}
$s = 0;
$valueToEncode = $value;
while ($s < $arrayLength) {
$valueToEncode = $salts[$s] . $valueToEncode;
$valueToEncode = openssl_encrypt($valueToEncode, $cipher_method, $enc_key, $options, $enc_iv);
$s++;
}
$return = $valueToEncode;
This is as close as I can get, it's 100% the first loop, but then the end of the ouput goes wrong when passed back through. I'm sorry if I'm missing something obvious, I'm pretty ok with PHP but encryption is new to me and I've searched and tried a lot and have come up empty.
I've tried implementing base64_decode/base64_encode to try and get closer to how Java is doing it), but it throws it all way out and I hope what I'm missing is easy and obvious to you clever folks.
I found the issue I was having was caused by Java creating line breaks (\n value) at the end of each iteration within the try loop.
These \n values were removed and now the outputs match.
FYI my PHP decrypt is this (forgive my lazy loop):
$s = $arrayLength - 1;
while ($s >= 0) {
$valueToDecode = openssl_decrypt($valueToDecode, $cipher_method, $enc_key, $options, $enc_iv);
$valueToDecode = substr($valueToDecode, $block_size);
$s--;
}
Thanks to those that offered feedback.
I have a strange problem in getting equivalent hash code from C# code translated into Java. I don't know, what MessageDigest update method do. It should only update the contents of digest and should compute hash after calling digest.
Same thing I am doing in C# with SHAManaged512.ComputeHash(content). But I am not getting same hash code.
Following is the Java code.
public static String hash(String body, String secret) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(body.getBytes("UTF-8"));
byte[] bytes = md.digest(secret.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException();
}
}
Following is C# Code
private byte[] ComputeContentHash(string contentBody)
{
using (var shaM = new SHA512Managed())
{
var content = string.Concat(contentBody, Options.SecretKey);
var hashedValue = shaM.ComputeHash(ToJsonStream(content));
return hashedValue;
}
}
public static Stream ToJsonStream(object obj)
{
return new MemoryStream(Encoding.Unicode.GetBytes(obj.ToString()));
}
I had the exact same issue. Its been two years since you asked but just in case someone comes across this question, here's the solution
public static string encryptHash(string APIkey, string RequestBodyJson)
{
var secretBytes = Encoding.UTF8.GetBytes(APIkey);
var saltBytes = Encoding.UTF8.GetBytes(RequestBodyJson);
using (var sHA256 = new SHA256Managed())
{
byte[] bytes = sHA256.ComputeHash(saltBytes.Concat(secretBytes).ToArray());
//convert to hex
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
builder.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
return builder.ToString();
}
}
The solution was to put first Secret Key and concate it with pay load data.
Encoding.Unicode (which you are using in the C# ToJsonStream method) is not UTF8. It's UTF16. See MSDN. (Also keep in mind that UTF16 can be little or big endian.) You're looking for Encoding.UTF8.
First thing to do is check if the byte array you're hashing is the same.
private String getString(byte[] bytes)
{
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++)
{
byte b = bytes[i];
sb.append(0xFF & b);
}
return sb.toString();
}
public String encrypt(String source)
{
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(source.getBytes());
return getString(bytes);
}
catch (Exception e)
{
e.printStackTrace(); }
return null;
}
If my text = "test"
The First Part toString()) produces a value of "Encryption$2#6966b26b"
And the second part then gets that and produces a value of "91431072057033211115202222781313839180246"
But why is the md5 a number and not 31f521a06d5060d1f38159c74a1f7cf2 or something similar?
The function "encrypt()" returns a MD5 hash. You should rename it to "hash", because hashing != encrypting.
If you want to encrypt a string, you can look here: https://gist.github.com/bricef/2436364
It's clearly stated in code yuou are using MD5 hashing algorithm
Now your question is why:
But why is the md5 a number and not 31f521a06d5060d1f38159c74a1f7cf2 or something similar?
your answer is simple, look at code which generates you string from your byte array.
byte b = bytes[i];
sb.append(0xFF & b);
you take byte, ie 0x20 then you perform logical and operation with integer 0x255 and then you add decimal representation of result yo your StringBuilder.
What you want to do is more like
sb.append(Integer.toHexString(0xff&b));
I would say MD5 hash, because the code says MessageDigest.getInstance("MD5") :D
Hello everybody I am working on the first piece of communication between server and client of my game. Obviously due to the fact that I am starting from zero, I am projecting each part of the program carefully.
I was looking in Swing API and I found the JPasswordField that is a normal InputField, but for passwords.
It returns as you know a string if the deprecated method getText() is called or an array of chars if is called getPassword.
Reading in SO I understood that is not a good idea to use getText, nor something like
String password = String.valueOf(passwordField.getPassword());
because doing so I am creating a String that can stay in memory for long time.
What I tried to create is something that can convert that password without using strings and I created this:
public static String digest(char[] in) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
ArrayList<Byte> list = new ArrayList<Byte>();
for(int i = 0; i<in.length; i++){
String ch = String.valueOf(in[i]);
byte[] b = ch.getBytes();
for(int j = 0; j<b.length;j++){
list.add(b[j]);
}
}
byte[] inputInByte = new byte[list.size()];
for(int i =0;i<list.size();i++){
inputInByte[i] = list.get(i);
}
md.update(inputInByte);
byte byteData[] = md.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < byteData.length; i++) {
String hex = Integer.toHexString(0xff & byteData[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
The question is: is this algorithm correct and good for the security of the password? I had to use a String to convert from char to byte.
Also I return an hashed string, is there any problem in that? It should be quite difficult to find the password starting from the hash ;)
How about database connection? Hsqldb allow me to create query, but each query is a string......
Using SHA-256 digest is a hash method, not a cryptologic one. It is like a fingerprint. Can you get a person from his fingerprints without testing everybody's (6 billions) fingerprint ? No. It used to store password in databases in php, for example. We just store the pass's hash, and when the user want to connect, we compute the newly entered password hash, and compare it with the database's hash.
This prevents users from stealing passwords if the database is hacked. But you cannot get the password from the hash. I hope i answered to your question.
By the way, consider using apache lib for message digest, it is easier and more safe i think
I think your code is quite ok, but you are still working with String to create the byte value, so you maybe better change String.valueOf(in[i]); to something like this:
public static String digest(char[] in) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
ArrayList<Byte> list = new ArrayList<Byte>();
for(int i = 0; i<in.length; i++){
byte b = (byte) in[i]
list.add(b);
}
byte[] inputInByte = new byte[list.size()];
for(int i =0;i<list.size();i++){
inputInByte[i] = list.get(i);
}
md.update(inputInByte);
byte byteData[] = md.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < byteData.length; i++) {
String hex = Integer.toHexString(0xff & byteData[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
that is also easier than using that for cycle and two step conversion to byte.