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.
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'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.
I've been strugling for a while now by trying to complete next goal :
I have a "Reset password" page that supposed to send new password to the server. I would like to hash it with salt, so I could save it in DB eventually. On Server side I have next methods that creates password hash :
public static String makeHash(String password, String salt) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(password.getBytes("UTF-8"));
byte byteData[] = md.digest(makeHash(salt.toLowerCase()));
return Base64.getEncoder().encodeToString(byteData);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
log.error("Unable to make hash for pass. No hashing.", e);
}
return password;
}
private static byte[] makeHash(String val) throws NoSuchAlgorithmException, UnsupportedEncodingException {
return MessageDigest.getInstance("SHA-256").digest(val.getBytes("UTF-8"));
}
I tried several Javascript libraries - crypto, crypto-js, SJCL , but couldn't manage to create same password as Java methods generates. For example, last working try out was :
var crypto = require('crypto');
crypto.pbkdf2('123', 'test#gmail.com', 1000, 60, 'sha256', function(err, key) {
if (err)
throw err;
console.log(key.toString('Base64')); // 'c5e478d...1469e50'
});
And it generated me this hash - Qr2lzotlRWj7BeJeFooMRj64auMPTb3PRhwLmfNcl4DCVAlFFibgOqZiyExZNO5i/icAUYoMjy73jSTd, while Java gives me - /pyQf3JCj5XoczfsYJ4LUb+y0DONGMl/AFzLiBTo8LA=.
I cannot change backend, since it running already for some time, so I was hoping that maybe someone could help me out with this.
You have to use the same algorithm on both sides. In Java you're using simply SHA-256 and in node you're using PBKDF2 with SHA-256.
Node.js' crypto module provides the createHash(algorithm) function. Where you can specify SHA-256 directly. PBKDF2 is an algorithm that only uses different hashing functions under the hood.
If you want hash passwords, then it is much safer to use PBKDF2 with a lot of iterations (> 86,000) and a random salt that you store alongside the password hash.
Java has support for PBKDF2 in its standard library.
If you really want to use SHA-256 directly and I strongly advise against it, you can use the following code:
var crypto = require('crypto');
var key = "123";
var salt = "test#gmail.com";
key = crypto.createHash('sha256')
.update(key, "utf8")
.update(makeHash(salt))
.digest("base64");
console.log(key);
function makeHash(val) {
return crypto.createHash('sha256').update(val, "utf8").digest();
}
Output:
/pyQf3JCj5XoczfsYJ4LUb+y0DONGMl/AFzLiBTo8LA=
Note that Hash.digest() takes an optional output encoding and not additional data.
I'm using AES Decryption on my Android Project to decrypt large string objects ( > 1 MB ).
I'm using this method :
public static String decryptAES(String cryptedString, byte[] byteArrayAESKey) {
try {
IvParameterSpec ips = new IvParameterSpec(General.InitVector.getBytes("UTF-8"));
SecretKey aesKey = new SecretKeySpec(byteArrayAESKey, "AES");
byte[] TBCrypt = Base64.decode(cryptedString, Base64.DEFAULT);
// Decryption cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
// Initialize PBE Cipher with key and parameters
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ips);
// Decrypt the cleartext
byte[] deciphertext = decryptCipher.doFinal(TBCrypt); // this may take a long time depending on string input length
return new String(deciphertext, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
Log.e("AES", "Decrypt failed : " + e.getMessage());
return "";
}
}
It works well, but on large encrypted strings, it takes a long time on many devices.
Is there a way to improve this method on android devices ? Should I cut the encrypted string to accelerate the process ? Should I use SpongyCastle ?
byte[] deciphertext = decryptCipher.doFinal(TBCrypt); Dont do that! Instead consider using streams, maybe directly to output file stream (if needed)?.
Is there a way to improve this method on android devices ?
Maybe, you could take a look here , and there's saying that the AES is pretty fast, though.
Should I cut the encrypted string to accelerate the process ?
Yes, this should be the problem. Usually you only have to encrypt the critical parts of the data. Maybe a refactor should resolve the question.
Should I use SpongyCastle ?
Don't know, but if i where you i would first look at the data encrypted.
I have an app with java and PHP files. The java files send content to the PHP files, and this one send the response to the java file, by HTTP everything. I have the response with JSON format.
I would like to encrypt the information and decode it in the other side, java->php and php->java(this is the most important) but I don't know how to do it.
Edit:
I am trying BLOWFISH, here is my code in PHP(crypt the data and send to Java) and Java(get the data and decode it)
PHP
$key = "this is the key";
$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $result_json, MCRYPT_MODE_ECB);
echo($crypttext);
JAVA
public String decryptBlowfish(String to_decrypt, String strkey) {
System.out.println(to_decrypt);
try {
SecretKeySpec key = new SecretKeySpec(strkey.getBytes(), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(to_decrypt.getBytes());
return new String(decrypted);
} catch (Exception e) {
System.out.println(e.getMessage());
;
return null;
}
}
System.out.println(decryptBlowfish(result, "this is the key"));
The result when I execute is:
Input length must be multiple of 8 when encrypting with padded cipher
or sometimes
Given final block not properly padded
Agreed with the comment that's what SSL is for see here for a client java application that uses SSL Certificate and encryption to connect to an HTTPS/SSL site: http://www.mkyong.com/java/java-https-client-httpsurlconnection-example/ next you might want to have an HTTPS/SSL php server this should help: http://cweiske.de/tagebuch/ssl-client-certificates.htm Or use this Opensource library: http://nanoweb.si.kz/
If the above fails then I don't know, but a last resort would be writing your own, you may never know how secure it really is?
You might want to use the same algorithm for decoding/decrypting namely "blowfish/ecb/nopadding" instead of "blowfish".
private static final String DECRYPTION_ALGORITHM = "blowfish/ecb/nopadding";
private static final String KEY_ALGORITHM = "blowfish";
private static byte[] decrypt(byte[] keyData, byte[] valueData) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(keyData, KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(DECRYPTION_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(valueData);
}
If you don't want SSL, which I recommend too, you can try this:
$str = 'hello world'; //your input data
$pass = 'haj83kdj843j'; //something random, the longer the better
$l = strlen($pass);
for ($i=0; $i<strlen($str); $i++)
{
$str[$i] = chr(ord($str[$i]) + ord($pass[$i % $l]));
}
It is fast and easy to write a coder/encoder in any language you want. The resulting string is a binary string so you might want to convert it using base64_encode or something. Should give quite good security.