I'm having some difficulty producing the same encrypted password using the PBKDF2 algorithm on both Java and PHP.
I'm using the following Java implementation to generate the hash with a random byte array which is 16 bytes in size. I'm then storing the hash and salt separately in a MySQL database, however when I go to do the same operation in PHP using the salt retrieved from the database, I get almost the exact same encryption except the hash has a leading 0 and I cannot for the life of me figure out why.
Java:
public String hashPassword(String password, byte[] salt){
char[] passwordChars = password.toCharArray();
PBEKeySpec spec = new PBEKeySpec(
passwordChars,
salt,
ITERATIONS,
KEY_LENGTH
);
SecretKeyFactory key = null;
try {
key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] hashedPassword = null;
try {
hashedPassword = key.generateSecret(spec).getEncoded();
} catch (InvalidKeySpecException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return String.format("%x", new BigInteger(hashedPassword));
}
I found the above code at https://adambard.com/blog/3-wrong-ways-to-store-a-password/
PHP:
$query = $database->query('SELECT * FROM USERS');
$password = 'hello';
$iterations = 1000;
foreach($query as $user){
$hash = hash_pbkdf2("sha1", $password, $user['salt'], $iterations, 40, false);
}
echo $hash;
Note: There is only one user stored in the database, I know the above code isn't great, I created it quickly for testing purposes.
For both implementations I'm using an iteration count of 1000, a key length of 160 in Java and a key length of 40 in PHP (to compensate for setting raw-output to false)
Java Output - 971f0dddc1bc2e899f2bca178f16ea79bfbbb13
PHP Output - 0971f0dddc1bc2e899f2bca178f16ea79bfbbb13
Any help is much appreciated, thank you.
It is the BigInteger that is killing the leading 0.
Hashes are not integers, they are an array of 8-bit bytes. Do not try to convert to a BigInteger.
Either use it as a byte[] or encode as a hexadecimal or Base64 string. To match PHP hexadecimal encode hashedPassword.
PHP is returning a hexadecimal string encoded hash because raw_output is set to FALSE.
Related
This question might probably be a duplicate. But so far I haven't seen any response that could solve mine issue.
I have this piece of Java code doing encryption with SHA-256:
public static String hashIt(String msg, String key) {
MessageDigest m = null;
String hashText = null;
byte[] actualKeyBytes = TripleDES.hexStringToBytes(key);
try {
m = MessageDigest.getInstance("SHA-256");
m.update(actualKeyBytes, 0, actualKeyBytes.length);
try {
m.update(msg.getBytes("UTF-8"), 0, msg.length());
} catch (UnsupportedEncodingException ex) {
}
hashText = TripleDES.bytesToHexString( m.digest() ); //new BigInteger(1, m.digest()).toString(16);
} catch (NoSuchAlgorithmException ex) {
}
return hashText;
}
Using d38a5cd5 as key with "ewo10kalavanda" as the string to hash.
Utils.hashIt("ewo10kalavanda", "d38a5cd5");
I have the following output:
fc87c73012e11de3a57faabe4d852ce89ec3337504531c16
Using the same SHA256 in PHP
hash_hmac('SHA256', "ewo10kalavanda", "d38a5cd5", $raw=false)
The output is 1839412f79b9e33c2f810650f79f23f46173792f885dd8d8c9633675e28e792f which does not match that of Java.
Is there anything done wrong here? Been on this for some hours now.
In your PHP code you used HMAC which is more than just hashing the string obtained by joining key and the message body. I found a diagram from Wikipedia which explains how HMAC-SHA1 works:
I did manage to get a working version in Java:
public static String hashIt(String msg, String key) {
try {
byte[] keyBytes = key.getBytes("UTF-8");
SecretKeySpec spec = new SecretKeySpec(keyBytes, HMAC_SHA256);
Mac mac = Mac.getInstance(HMAC_SHA256);
mac.init(spec);
return TripleDES.bytesToHexString(mac.doFinal(msg.getBytes("UTF-8")));
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
I still think there is something wrong with msg.length(). If you ever hash a two byte character it will be buggy. I tested it and it's different. For example try use your previous code to hash 111111錒(message) and 1111(key) and then use my previously suggested code to hash the same string. Your code output 81e385eb2bf89f7494a4b0927a4f5d4105450eb4a21152d53d52ddb9c08ed0e1 and my code output ef7f82833c865ef4d6089ba7dfbec8ad4f05b58e3fd77ca242c5fd7e7757d8b4.
That chinese character is intended. It shows how the OP's code fails with two byte characters. DO NOT REMOVE.
The other answer from glee8e should have got you a long way. But just to be sure, here is how to generate the output:
$k = hex2bin("d38a5cd5");
$m = "ewo10kalavanda";
$in = $k.$m;
$h = hash ("SHA256", $in);
print $h;
It would be a bit better to first encode to UTF-8, but I haven't got the right module installed:
$m = mb_convert_encoding("ewo10kalavanda", "UTF-8");
for the test sting this of course doesn't matter as long as the platform encoding is compatible with UTF-8 for the input characters.
That's however half of the answer though: there is a reason why HMAC was defined, and the major reason is that hash functions on their own are not that secure for keyed hash or Message Authentication Code (MAC). So the use of HMAC as in the PHP function should be preferred.
i am using android to create a key pair, i use http post to send the public key to a wamp server mysql data base using a php script.
after successfully receiving the key, the php scripts encrypts a string using the key and encoding it with base43, the sripts echos a json object to android.....where i decode using base64 and then use the private key to decrypt the text and then base64encode it again to view it.
php
$rawKey = $_POST['rawKey'];
$publicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" . chunk_split($rawKey) .
"-----END RSA PUBLIC KEY-----";
$rsa = new Crypt_RSA();
$rsa->loadKey($publicKey); // public key
$AESKeyString = "some text";
$AESKeyString = $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$ciphertext = $rsa->encrypt($AESKeyString);
$ciphertext = base64_encode($ciphertext);
$response = array('' => $ciphertext);
echo json_encode($response);
java
public String Decrypt(String encryptedKey) {
Cipher cipher = null;
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} try {
cipher.init(Cipher.DECRYPT_MODE, privKey);
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] cipherData = null;
try {
cipherData = cipher.doFinal(Base64.decode(encryptedKey, Base64.NO_WRAP));
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String cipherString = Base64.encodeToString(cipherData, Base64.NO_WRAP);
Log.d("SecCom", cipherString);
return cipherString;
}
the problem WAS that although there were no errors in decryption the text was always garbage....however this garbage was unique and same for a give plaintext....that is to say "hello world" in java would always translate to "n;lk#;la" and only changing the plain text would change the decrypted garbage.
i looked at numerous examples on stackoverflow and this seemed to work for them and purely out of some gut feeling i added a base64 decode to the php string before encrypting it
$AESKeyString = base64_decode("some text");
and viola this solved the problem except for the fact that now i get origional string in java except that all the spaces are removed..... and the last character is replaced by g==
that is "some text" appears as "sometexg=="
i have tried numerous texts but this is constant through all no spaces and last character replaced by g==
in my final php script i will be generating random bytes for aes, encrypting them and then encoding them to send to java. please keep this in mind in the solution that you provide me.....
also why did adding base64.decode in php was necessary for me yet for others it worked just out of the box
thanks
as i mentioned in my comments.... my problem was not understanding the String and encoding part and how it tranlates to java.....
well i gave it a go, did a bit of reading and rewrote the php side and it worked in the first attempt.....
.... here is a full working php code....the java code does not need to be changed....i have also commented it
php
//get the posted public key
$pumpumString = $_POST['pumpum'];
//format public key in CRYPT format
$publicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" . chunk_split($pumpumString) .
"-----END RSA PUBLIC KEY-----";
//initialise Algorithm
$rsa = new Crypt_RSA();
$rsa->loadKey($publicKey); // public key
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
//generate new AES Session Key
$AESKey = rand_sha1(32); //a custom created function
$AESKeyDecoded = base64_decode($AESKey);
//encrypt AES Session Key
$ciphertext = $rsa->encrypt($AESKeyDecoded);
//base 64 encode it for transfer over internet
$ciphertextEncoded = base64_encode($ciphertext);
//prepare array for sending to client
$response = array('plum' => $ciphertextEncoded);
//write the encoded and decoded AES Session Key to file for comparison
$file = fopen('key.txt', 'w');
fwrite($file, "\n". $AESKey);
//echo JSON
echo json_encode($response);
one of the things that puzzled me earlier was why i needed to base64 decode my plaintext before encryption....well what i have been able to figure out is that, php probably uses ASCII to store strings. since in java i am using a base64_encode to get the decrypted string from the decrypted byte array.....i need to first decode my ascii plaintext string to regenerate it in java......(i might have worded that a bit non-coherently...please feel free to reword it.)
if u feel that i have come to the wrong conclusion or something can be bettered please let me know, i am marking this as solved....
also i had asked for a way to generate a random aes key.....below is the function i used to do it.....courtesy https://stackoverflow.com/a/637322/2208279
php
function rand_sha1($length) {
$max = ceil($length / 40);
$random = '';
for ($i = 0; $i < $max; $i ++) {
$random .= sha1(microtime(true).mt_rand(10000,90000));
}
return substr($random, 0, $length);
}
i am using a aes256 so i have used 32 as the argument to this function....modify it to 16 for 128
thanks....hope this helps someone.
I am not an expert in cryptography and I am getting some interesting results when I use the encryption method below.
The server is .NET C# and the client runs JAVA. Basically, We encrypt credit card information and for the 12 credit cards I have, 11 works perfectly with the methods below.
However, one of the cards (real VISA credit CARD) the result returned by encrypt() and converted to hex has a negative symbol in the start of the string, like this:
-6d9830a52b2c3add7a78fd9897bca19d....., it fails when the server tries to decrypt it and I think it should be positive not negative based on this explanation RSA - Encryption with negative exponent
private static byte[] encrypt(String text, PublicKey pubRSA) throws Exception
{
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubRSA);
return cipher.doFinal(text.getBytes());
}
//Using this encryption method one card could not be decrypted by vPAY due to negative (exponential) symbol.
//It may have the same affect with other cards
public final static byte[] encrypt(String text)
{
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(Base64.decode(pkBase64));
PublicKey pk = keyFactory.generatePublic(x509Spec);
return encrypt(text, pk);
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
Has anyone faced something like that and found a workaround?
I have tried three other algorithms with different KeySpec and the same publicKey (the source is a string in base64 format) but none of them could be decrypted by the server even with the cards the were working before...
UPDATE 1
This is how a convert the encrypted result in bytes to HEX:
public static String byteToHex(byte[] string)
{
try {
return String.format("%04x", new BigInteger(string));
} catch (Exception e) {
// TODO Auto-generated catch block
return null;
}
}
You should print out the hexadecimal string directly from byte[]. This can be done using the following code:
StringBuilder sb = new StringBuilder(data.length * 2);
for (int i = 0; i < data.length; i++) {
sb.append(String.format("%02X", data[i] & 0xFF));
}
return sb.toString();
There is no need to use BigInteger. In fact, it is dangerous to use BigInteger. One reason is the one you've already encountered: BigInteger conversion to/from byte[] is using signed big endian encoding by default. The other thing is that the output of the RSA signature (as integer) may be smaller than the modulus size in hexadecimals. This is why EJP's solution will fail now and then.
RSA output has been defined in bytes, as an unsigned big endian encoded in the same number of bits as the key size (using integer to octet string encoding in the standard documents).
public static String byteToHex(byte[] string)
A byte[] is not a string. It's a byte array. Don't confuse yourself with inappropriate variable names. String is not a container for binary data.
return String.format("%04x", new BigInteger(string));
Try return new BigInteger(1,string).toString(16), and have a look at the Javadoc to see why this works where new BigInteger(string) didn't.
I have am trying to use PBKDF2 to store passwords. I am then using the code with the password hashes it generated on a different machine.
I am using this method to encrypt my passwords:
public String pwdEncodePBKDF2(String unencryptedPassword,String salt)
{
try
{
if(salt.isEmpty())
{
salt = generateSalt(SystemSecurity.SALTLENGTH);
}
String algorithm = "PBKDF2WithHmacSHA1";
int derivedKeyLength = 160;
int iterations = 1000;
KeySpec spec = new PBEKeySpec(unencryptedPassword.toCharArray(), salt.getBytes(), iterations, derivedKeyLength);
SecretKeyFactory f = SecretKeyFactory.getInstance(algorithm);
StringBuffer hexString = new StringBuffer();
byte[] mdbytes = f.generateSecret(spec).getEncoded();
for (int i=0;i<mdbytes.length;i++)
{
hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
}
String hashedPassword = hexString.toString();
return hashedPassword + salt;
}
catch(Exception e)
{
e.printStackTrace();
throw new RuntimeException("Error computing hash: "+e.getMessage());
}
}
It works fine, but when I run it on a different machine (i.e. install my project on a different machine, with a database that has an encrypted of a default password from the machine I run on initially)
I see that with the same salt and password it give me a different encryption.
As far as I understand the SecretKeyFactory methods depend only on the inputs I give them, or do they depend on the machine I am running on as well?
If so, how can I save a default password for first installation with this security mechanism without running any extra code during installation?
Thank You!
I think the problem may be in different default String encodings.
Check that your strings use same encoding.
you can try to check bytes using
salt.getBytes()
it return bytes in default encoding, may be machines has different encodings.
You can just replace salt.getBytes(), with somethink like salt.getBytes("UTF-8"); may be it will help.
In my Android app I have a SHA256 hash which I must further hash with the RIPEMD160 message digest algorithm.
I can output the correct sha256 and ripemd160 hash of any string, but when I try to hash the sha256 hash with ripemd160 I get a hash which is incorrect.
According to online hash calculators, the SHA256 value of the string 'test'(all lowercase) is:
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
And the RIPEMD160 value of the string 'test' is:
5e52fee47e6b070565f74372468cdc699de89107
The value from hashing the resulting sha256 hash with ripemd160 according to online calcs is:
4efc1c36d3349189fb3486d2914f56e05d3e66f8
And the one my app gives me is:
cebaa98c19807134434d107b0d3e5692a516ea66
which is obviously wrong.
Here is my code:
public static String toRIPEMD160(String in)
{
byte[] addr = in.getBytes();
byte[] out = new byte[20];
RIPEMD160Digest digest = new RIPEMD160Digest();
byte[] sha256 = sha256(addr);
digest.update(sha256,0,sha256.length);
digest.doFinal(out,0);
return getHexString(out);
}
public static byte[] sha256(byte[] data)
{
byte[] sha256 = new byte[32];
try
{
sha256 = MessageDigest.getInstance("SHA-256").digest(data);
}
catch(NoSuchAlgorithmException e)
{}
return sha256;
}
For the ripemd160 algorithm, you need bouncycastle and java.security.MessageDigest for sha256.
Your "online calculator" result is the result of hashing the bytes of the string "test" with SHA-256, converting the result of that hash to a hex string, then taking the bytes corresponding to the ASCII characters of that hex string and hashing those a second time. This is very different from your Java code, which passes the bytes that come out of the first hash directly to the second one, without printing them as hex and turning those characters back into bytes in between. The single byte with value 254 (decimal) becomes "fe" in hex, which becomes the two-byte sequence [0x66, 0x65] when converted back to bytes.
Your hash is working fine. The problem is that the online calculators that you're using are treating your input:
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
as a string instead of an array of bytes. In other words, it's treating each character as a byte instead of parsing character pairs as bytes in hexadecimal. If I give this as a string to online calculators, I indeed get exactly what you got:
4efc1c36d3349189fb3486d2914f56e05d3e66f8
However, you're treating the output as an array of bytes instead of a String and that's giving you different results. You should encode your raw SHA256 hash as a string, then pass the encoded string to the hash function. I see you have a getHexString method, so we'll just use that.
public static String toRIPEMD160(String in) {
try {
byte[] addr = in.getBytes();
byte[] out = new byte[20];
RIPEMD160Digest digest = new RIPEMD160Digest();
// These are the lines that changed
byte[] rawSha256 = sha256(addr);
String encodedSha256 = getHexString(rawSha256);
byte[] strBytes = encodedSha256.getBytes("UTF-8");
digest.update(strBytes, 0, strBytes.length);
digest.doFinal(out, 0);
return getHexString(out);
} catch (UnsupportedEncodingException ex) {
// Never happens, everything supports UTF-8
return null;
}
}
If you want to know it's working, take the value of encodedSha256 and put that into an online hash calculator. As long as the calculator uses UTF-8 encoding to turn the string into a byte array, it will match your output.
To get printable version of byte[] digest use this code:
StringBuffer hexString = new StringBuffer();
for (int i=0;i<out.length;i++) {
hexString.append( String.format("%02x", 0xFF & out[i]) );
}
and then call hexString.toString();