I find myself in a need to change website platforms from Java to PHP but I'd like to keep all my user's passwords...
I had this code do the password hashing prior to writting the hashed value as the password to the website:
MessageDigest md = null;
md = MessageDigest.getInstance("SHA");
md.update(plaintext.getBytes("UTF-8"));
byte raw[] = md.digest();
hash = new Base64().encodeToString(raw).replaceAll("\n", "").replaceAll("\r", "");
I think the Java code did SHA-1 hashing of the password but just prior to that it was byte encoded to UTF-8 and afterwards it was Base64 encoded.
I'd like to have a PHP code do the same, i.e. return the same value of a hash for the same password as in Java, only it seems that the PHP code doing SHA-1 hashing I have won't return the same SHA(-1, not Base64 encoded, I think?) value when compared to a Java Base64 decoded value of the hash...could it have something to do with the fact that my passwords in PHP are not UTF-8 byte encoded first (and how can I do that in PHP) please?
p.s.
Another strange thing...my passwords in Java are all 28characters long (usually something like this rnwn4zTNgH30l4pP8V05lRVGmF4=)...but the Base64().decode(hash) value of those password hashes is 10 characters long (an example [B#14e1f2b).
I thought Base64 did an additional 1 character to each 3 charters (28 or 27, excluding the padding = charter, is much more that a third larger than those 10 charcters) so am I doing the decoding call wrong somehow maybe???
And on top of all that the SHA-1 password hashed values in PHP are 40 characters long (in a UTF-8 mysql database) like so dd94709528bb1c83d08f3088d4043f4742891f4f?
[B#14e1f2b is definitely not a hash. It's a result of implicit conversion from byte[] to String.
It looks like you do something like this:
String decodedHash = Base64().decode(hash); // Produces [B#14e1f2b
However, the correct representation of the hash is a byte array:
byte[] decodedHash = Base64().decode(hash);
What I normally do with Java to compute a SHA-1 hash that is exactly identical to the PHP sha1() function is the following. The key is that toHexString is used to show the raw bytes in a printable way. If you use the PHP function and want to obtain the same result of your convoluted process, you need to use the parameter $raw_output to true in PHP to get the raw bytes and apply Base64. Full source code.
/**
* Compute a SHA-1 hash of a String argument
*
* #param arg the UTF-8 String to encode
* #return the sha1 hash as a string.
*/
public static String computeSha1OfString(String arg) {
try {
return computeSha1OfByteArray(arg.getBytes(("UTF-8")));
} catch (UnsupportedEncodingException ex) {
throw new UnsupportedOperationException(ex);
}
}
private static String computeSha1OfByteArray(byte[] arg) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(arg);
byte[] res = md.digest();
return toHexString(res);
} catch (NoSuchAlgorithmException ex) {
throw new UnsupportedOperationException(ex);
}
}
private static String toHexString(byte[] v) {
StringBuilder sb = new StringBuilder(v.length * 2);
for (int i = 0; i < v.length; i++) {
int b = v[i] & 0xFF;
sb.append(HEX_DIGITS.charAt(b >>> 4)).append(HEX_DIGITS.charAt(b & 0xF));
}
return sb.toString();
}
PHP's sha1() encodes each byte of the output as hexadecimal by default, but you can get the raw output by passing true as the second argument:
$digest = sha1($password, true); // This returns the same string of bytes as md.digest()
Then pass the digest to base64_encode and you are done:
base64_encode(sha1($password, true));
This returns the exact same SHA-1 hash as your java code.
Related
My application uses salted hash in Java. First a random salt is generated. Then this salt is prefixed to the SHA-512 of the input password and the combined string is SHA-512 again.It is implemented as follows:-
String password = testpwd.getText().toString();
SecureRandom rand = new SecureRandom();
byte[] randbytes = new byte[16];
rand.nextBytes(randbytes);
String encodedSalt = Base64.encodeToString(randbytes, Base64.DEFAULT);
MessageDigest digest = MessageDigest.getInstance("SHA-512");
digest.reset();
digest.update(password.getBytes("utf-8"));
byte[] pwdhash = digest.digest();
String encodedPwd = Base64.encodeToString(pwdhash, Base64.DEFAULT);
String saltedPassword = encodedSalt+encodedPwd ;
digest.reset();
digest.update(saltedPassword.getBytes("utf-8"));
byte[] pwdhash1 = digest.digest();
String encodedPwdSalt = Base64.encodeToString(pwdhash1, Base64.DEFAULT);
Then the strings encodedSalt and encodedPwdSalt are sent to web server for authentication. The PHP encryption is as follows:
$postpassword = $_POST['password'];
$postsalt = $_POST['salt'];
$salt = base64_decode($postsalt);
$password = base64_decode('postpassword');
The SHA-512 hashs of the password "Ditglt#785" is stored in the database. It is retrieved and processed as follows:-
$getsaltpwd = $salt.$dbpassword ;
$dbsaltpwd = hash('sha512', $getsaltpwd);
if($dbpassword == $postpassword) {}
The condition always fails and so does the authentication. What should I do ?
The PHP version hashes raw bytes while the Java version hashes base64-encoded strings.
Here's a Java version that matches what your PHP code does:
digest.reset();
digest.update(randbytes);
digest.update(pwdhash);
byte[] pwdhash1 = digest.digest();
String encodedPwdSalt = Base64.encodeToString(pwdhash1, Base64.DEFAULT);
Having said that, it would be more secure to store the salt and the salted password in the database, and to use at least some key derivation function (iterate the hash function many times) in order to counteract any potential brute-forcing of the stored hashes.
Since your Java code correctly follows what you describe in your specification, the problem lies on the PHP side.
With your Java code as-is, it generates the following values when encoding the string "password" with a random salt:
encodedSalt: ww0g+f77ygKD7Iww1GTYtg==
encodedPwd: sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==
encodedPwdSalt: YAGG7GcpUxIZzBnHuaezPf5BWFhFalBPgvue/0wFoRLu+JsKslG8wPCv6dPubIBk1aFIJ8spK8S17347aDBAYA==
In PHP, what you would need to do is the following:
$postpassword = 'YAGG7GcpUxIZzBnHuaezPf5BWFhFalBPgvue/0wFoRLu+JsKslG8wPCv6dPubIBk1aFIJ8spK8S17347aDBAYA==';
$postsalt = 'ww0g+f77ygKD7Iww1GTYtg==';
$dbpassword = 'sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==';
if($postpassword == base64_encode(hash('sha512', $postsalt.$dbpassword, true))) {
echo 'OK';
}
Check if the padding matches. I experienced the same problem with encryption where the padding in PHP was different from the padding in JAVA. Luckily I was able to set the padding in JAVA to the one that PHP uses. But I had to look at PHP source code to figure out how. As far as I remember it was not possible to change the padding in PHP back then.
Here is the question I posted back then: decrypting php encrypted data on android
[...] you need to set Base64.decode with the parameter Base64.NO_WRAPas PHP will just put out the base64 delimited by \0.
I've written this simple Java snippet to SHA-256 a string:
public static void main(String[] args) throws NoSuchAlgorithmException {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
String input = "00010966776006953D5567439E5E39F86A0D273BEE";
byte[] output = sha256.digest(input.getBytes());
System.out.println(new String(output));
}
Running SHA-256 using this tool gives the output 3CC2243D50E87857A233965AA6B68B37563BFCC52B3C499FBB259B9AA87FFF40, but when I run it myself I get <�$=P�xW�3�Z���7V;��+<I��%����#. It looks like something is going wrong with the byte conversion, but I'm not exactly sure what.
You are correct that something was wrong when you tried to convert byte[] to string. Here is a code that works :)
public static void main(String[] args) throws NoSuchAlgorithmException {
final String input = "Nishit";
final MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(input.getBytes());
final byte[] data = md.digest();
StringBuilder sb = new StringBuilder(data.length * 2);
for (byte b : data) {
sb.append(String.format("%02x", b));
}
System.out.println(sb.toString());
}
What it is really happenning is that the SHA256 returns a 256-bit hash value. So what you're printing is those bytes as if they were characters and their respective character values is all that gibberish.
What the online tool is returning you is the representation of that value in hexadecimal format.
Notice that you're getting, (with the tool) 64 bytes IE 64 characters when 256-bit is equal to 32 bytes (32 charaters you may think).
That is because to represent a whole byte in hexadecimal format 2 characters are needed. 4 most significative bits take one character and the other less significative bits take another one.
I've been given a chunk of code which describes an "algorithm" for token generation. This code is written in Java and works as is correctly. The given Java code must remain exactly as is, however, my requirement is to have the same "algorithm" used within a PHP application and I'm having a hard time retrieving the same result.
Java code:
public static void main(String[] args)
throws
UnsupportedEncodingException,
NoSuchAlgorithmException {
// Let's stick to a fixed date for this SO question
//DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmm");
//dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
//String date = dateFormat.format(new Date());
String date = "201603251605";
String name = "some_dummy_data_one";
String size = "some_dummy_data_two";
// MD5 'name'
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(name.getBytes());
byte[] md5name = md5.digest();
// What happens here is beyond me - How would one translate this to PHP?
byte[] sizeBytes = (size + date).getBytes();
byte[] tokenBytes = new byte[md5name.length + sizeBytes.length];
System.arraycopy(sizeBytes, 0, tokenBytes, 0, sizeBytes.length);
System.arraycopy(md5name, 0, tokenBytes, sizeBytes.length, md5name.length);
// SHA256
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(tokenBytes);
byte[] tokenHash = sha256.digest();
System.out.println(Base64.getEncoder().encodeToString(tokenHash));
}
This prints:
LsPw/5/4uKmQfVaU1LRASxG89mgMt7OxX+h7JGRo1ZU=
Ultimately and ideally I'd love a quick way to just get the same result with PHP code, but I've the urge to understand what's going on instead of just reaching the desired result.
Now to attempt to display that I have at least done a little homework and I'm not just seeking someone to do my work for me, up until the MD5 conversion of the strings, I've managed to get an identical base64 encoded SHA256 string of a single string skipping the middle chunk of code containing the 'System.arrayCopy' bit:
Java code:
public static void main(String[] args)
throws
UnsupportedEncodingException,
NoSuchAlgorithmException {
String date = "201603251605";
// MD5 'name'
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(date.getBytes());
byte[] md5Date = md5.digest();
// SHA256
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(md5Date);
byte[] tokenHash = sha256.digest();
System.out.println(Base64.getEncoder().encodeToString(tokenHash));
}
PHP code:
private function generateToken() {
$md5Date = md5("201603251605", true);
$shaString = hash("sha256", $md5Date, true);
return base64_encode($shaString);
}
Java output:
mEkkRlBkwyoWFMsA+v/hP/m9sD6FdzM6LZHIORtr260=
PHP output:
mEkkRlBkwyoWFMsA+v/hP/m9sD6FdzM6LZHIORtr260=
So according to the above, my md5 and sha of a single string works fine, but that's not the problem. The problem is understanding what is happening when multiple strings are defined and are being mashed together in the Java code. Why does it seem overly complicated and is there a simple way of doing it in PHP?
Could somebody please explain what is happening in between the MD5 and SHA256 generation so that I can translate it to PHP code? I've attempted to read up on the Java System.arrayCopy documentation, but I feel as though I'm not experienced/smart enough to understand what is going on there. If there's a simple solution to this, I'd appreciate it very much.
You'll kick yourself when you realize how simple what it's actually doing is.
<?php
$date = "201603251605";
$name = "some_dummy_data_one";
$size = "some_dummy_data_two";
$md5_name = md5($name, true);
$token_bytes = $size.$date.$md5_name;
$token_hash = hash("sha256", $token_bytes, true);
echo base64_encode($token_hash);
arraycopy is just a pretty confusing way of appending size, date and md5name. Why the Java code is so complicated I have no idea.
Sounds like you got the answer for how to do it in PHP. The java code does basically the same thing. Here's what it's doing...
// This concatenates size+date and the md5 hash, and then creates a SHA-256 hash from that value.
byte[] sizeBytes = (size + date).getBytes(); //Get byte array of size and date concatenation
byte[] tokenBytes = new byte[md5name.length + sizeBytes.length]; //Create the right size byte array to fit md5 and size+date
System.arraycopy(sizeBytes, 0, tokenBytes, 0, sizeBytes.length); // copies the size+date byte array into the first part of the token bytes byte array.
System.arraycopy(md5name, 0, tokenBytes, sizeBytes.length, md5name.length); //concatenates the md5 hash and size+bytes into the tokenBytes array
// SHA256
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");//Get the SHA-256 algorithm instance
sha256.update(tokenBytes); // give it a value to hash
byte[] tokenHash = sha256.digest(); // create SHA-256 hash from token bytes
Im trying to get an MD5 with java and I receive a negative value.
Can an MD5 result in a negative value?
This is my code:
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] sigBytes = md5.digest((sharedSecret+"api_key"+API_KEY).getBytes());
api_sig = new BigInteger(sigBytes).toString(16);
Thank You.
new BigInteger(sigBytes) is interpreting your signature as a signed integer, so yes, it would be possible to get a negative number. If you want Your string to contain the hex representation of your md5 value, have a look at some of the answers here: How can I generate an MD5 hash?
As jbowes alluded to, new BigInteger(sigBytes) is interpreting the signature as a signed integer (meaning the first bit denotes whether the number is positive or negative). If you want it to interpret the bytes as an unsigned number, you should use new BigInteger(1, sigBytes) instead.
I use following method to get right MD5 hash String (and it never gave me "negative" value):
public static String createMD5Hash(String input) {
try {
MessageDigest m = MessageDigest.getInstance("MD5");
byte[] out = m.digest(input.getBytes());
return new String(Base64.encodeBase64(out));
} catch (NoSuchAlgorithmException e) {
return null;
}
}
Note that Base64 is class from apache commons:
import org.apache.commons.codec.binary.Base64;
I have a sample application which generates a SHA1 hash in PHP as follows.
base64_encode(pack('H*', sha1($pass)));
I tried to achieve the same in Java, but so far, the output is different. The approach I used is as follows (Base64 and Hex classes come from commons-codec library).
byte[] rawSHA = null;
byte[] base64HexSHA = null;
String hex = null;
MessageDigest md= null;
// Get Message Digest Instance.
try {
md = MessageDigest.getInstance(SHA1_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
LOG.error("Unable to load SHA-1 Message Digest : " + e.getMessage(), e);
throw new IllegalStateException("SHA-1 Message Digest Instance Not Found");
}
// Build SHA1 Hash
rawSHA = md.digest(rawText.getBytes("UTF-8"));
// Convert to HEX
hex = new String(Hex.encodeHex(rawSHA));
// Encode to Base 64
base64HexSHA = Base64.encodeBase64(hex.getBytes("UTF-8"));
// Return String
return new String(base64HexSHA);
My question is, would the approach I have taken yield the same output as PHP's pack() function? My guess is that PHP pack() function returns the raw bytes where as the Hex.encodeHex returns hex string form (ref : http://www.w3schools.com/php/func_misc_pack.asp).
How can I achieve the same output as PHP's pack() function in Java (or the full output of the above PHP code) ?
Convertion to HEX is not required, just use this:
base64HexSHA = Base64.encodeBase64(rawSHA);