I've set up a fairly basic password hashing and salting system for saving passwords in my database. It works fine, but I'm not so sure about how the salt is being stored.
At the moment I'm creating it in a byte[] array, then converting that to a String, which is then stored in the database.
My only concern is that every salt starts with [B#, for example: [B#b24f11 or [B#1e71a51
Is it ok to store it like this, or should I also be leaving it as a byte[] array and storing it as binary data in the database? Or even doing something else?
public class PasswordHasher {
// calculates a hash, given a password and a salt
public static String getHash(String pass, String salt) {
String hashedPassword = null;
try{
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(salt.getBytes()); //update digest to include salt
byte[] hashedBytes = md.digest(pass.getBytes());
// convert byte array to hex
StringBuilder sb = new StringBuilder();
for (int i=0;i<hashedBytes.length;i++) {
sb.append(Integer.toHexString((int) hashedBytes[i] & 0xFF));
}
hashedPassword = sb.toString();
}catch(NoSuchAlgorithmException e){
e.printStackTrace();
}
return hashedPassword;
}
// calculates a hash, then returns both hash and salt to store in DB
public static String[] registerHashAndSalt(String pass){
String salt = getSalt();
String hashedPassword = getHash(pass, salt);
String[] hashAndSalt = {hashedPassword, salt};
return hashAndSalt;
}
// creates a random salt
private static String getSalt(){
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt.toString();
}
}
You're not transforming the bytes to a string. You're calling toString() on a byte[], which returns the type of the array ([B) followed by the # symbol and by the hashCode of the array.
Use base 64 or Hex encoding to transform bytes to a printable string.
Related
I'm working on an assignment for school regarding security and how to crack passwords using a dictionary. We are provided six hashed passwords, a dictionary of words, and a salt hash value. Three of the passwords are not salted, I have already cracked those. I am having trouble finding the three salted passwords.
I have my code set up to iterate through the large dictionary and hash each word and then compare to the hashed passwords. I've tried to hash the salt value on to the front and/or back of each word that I am hashing for comparison, but I can't find any matches. We're using three different algorithms (MD-5, SHA-256, and SHA-1). My program checks the length of the password hash and routes it to which algorithm needs to be used for cracking.
Here's an example of what I have running for the salted MD-5 password:
MessageDigest md = MessageDigest.getInstance("MD5");
md.update((word + salt).getBytes());
byte[] bytes = md.digest();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff)
+ 0x100, 16).substring(1));
}
return sb.toString();
"word" is the password sent in to be checked, "salt" is the salt hex in a string. This returns the value after hashing to a for loop that is running through my dictionary looking for a match. I can't understand why it can find the three unsalted versions but appending the salt to the front (or back) wont work. Obviously I'm misunderstanding something here. Any clarity is greatly appreciated. Thanks!
Try something like this:
MessageDigest md5 = MessageDigest.getInstance("MD5");
Base64.Encoder base64 = Base64.getEncoder();
ShadowRecord r = new ShadoRecord("username", "usedSalt", "hashedPassword");
if (test(r, "secret")) {
System.out.println("Password is 'secret'");
}
public class ShadowRecord {
public final String username;
public final String salt;
public final String hash; // result of hash(password + salt)
public ShadowRecord(String username, String salt, String hash) {
this.username = username;
this.salt = salt;
this.hash = hash;
}
}
public boolean test(ShadowRecord r, String pwd) {
String input = pwd + r.salt;
String result = hash(input);
return result.equals(r.hash);
}
// input = pwd + salt
public String hash(String input) {
byte[] result = md5.digest(pwd.getBytes(StandardCharsets.UTF_8));
byte[] hash = base64.encode(result);
return new String(hash, StandardCharsets.UTF_8);
}
I'm trying to make a simple but secure login system. I've read that hashing and salting passwords gives sufficient security if you're using a good algorithm for hashing and creating a unique salt for each hash. I found this code-snippet on the OWASP website for the hashing method:
public static byte[] hashPassword(final char[] password, final byte[] salt, final int iterations, final int keyLength) {
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
SecretKey key = skf.generateSecret(spec);
byte[] res = key.getEncoded();
return res;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
and I'm using SecureRandom to generate the salt
public static byte[] generateSalt(int length) {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[length];
random.nextBytes(salt);
return salt;
}
This is where my question comes
I would like to store the hashed password and salt to a database using JDBC.
I'm not sure which datatype to use in the databse (varchar? blob? something else?)
I've tried storing the byte array as a varchar and reading it as a String, but when i output the result i get all question marks, so i guess that's not the way to do it.
A blob looks right, considering its storing bytes. But all the examples i find seem to use it for storing images so i was thinking there might be another approach for byte arrays? What's the way to do it?
Here's a sample dummy code called test(), I run this 100K times and I get different encrypted messages for the same plain text (obviously, the decryption I get is the original plain text).
I guess the reason behind this is to avoid frequency; but how come there can be MANY encryption to ONE decryption? shouldn't it be a one to one?
public static void test()
{
String plainMessage = "I'm gonna bid 100 USD on this project";
String password = "A99922000001000004581F0F0CCD0000";
Set<String> set = new HashSet<String>();
for (int x=0; x<100000; x++)
{
String enc = AESEncryption.encryptMessage(plainMessage, password);
System.out.println(enc);
set.add(enc);
String dec = AESEncryption.decryptMessage(enc, password);
if (!dec.equals(plainMessage))
{
System.out.println("wrong decryption"); //never gets here
break;
}
}
System.out.println(">"+set.size()); //just to make sure they are all unique
}
the encryption
public static String encryptMessage(final String plainMessage, final String symKeyHex)
{
final byte[] symKeyData = DatatypeConverter.parseHexBinary(symKeyHex);
final byte[] encodedMessage = plainMessage.getBytes(Charset.forName("UTF-8"));
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
// create the key
final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");
// generate random IV using block size (possibly create a method for
// this)
final byte[] ivData = new byte[blockSize];
final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
rnd.nextBytes(ivData);
final IvParameterSpec iv = new IvParameterSpec(ivData);
cipher.init(Cipher.ENCRYPT_MODE, symKey, iv);
final byte[] encryptedMessage = cipher.doFinal(encodedMessage);
// concatenate IV and encrypted message
final byte[] ivAndEncryptedMessage = new byte[ivData.length + encryptedMessage.length];
System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage, blockSize, encryptedMessage.length);
final String ivAndEncryptedMessageBase64 = DatatypeConverter.printBase64Binary(ivAndEncryptedMessage);
return ivAndEncryptedMessageBase64;
}catch (InvalidKeyException e)
{
throw new IllegalArgumentException("key argument does not contain a valid AES key");
}catch (GeneralSecurityException e)
{
throw new IllegalStateException("Unexpected exception during encryption", e);
}
}
To add some clarity to the comments:
The iv or initialization vector is like a second key which is used to encrypt the data.
Since you use a random iv each time (this is good), you get different encrypted text each time. This second key is actually included with the encrypted text, so you don't need to pass it along separately.
Having just the iv doesn't enable you to crack the encryption (which is why you can pass it along with the encrypted text), but by using it, you can send the same plain text with the same password multiple times (using different ivs) and get completely different encrypted values.
I have a pre-written code which is used to cipher the given plain text or vice-versa .
The class has 3 methods, where in 2 methods can be used for encrypting and decrypting respectively.
public class SqlCipherUtil {
private Cipher ecipher;
private Cipher dcipher;
public String encryptString(String pStrPlainText) {
try {
generateKey();
byte[] utf8 = pStrPlainText.getBytes("UTF8");
byte[] enc = this.ecipher.doFinal(utf8);
return new BASE64Encoder().encode(enc);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String decryptString(String pStrCipherText){
try {
generateKey();
byte[] dec = new BASE64Decoder().decodeBuffer(pStrCipherText);
byte[] utf8 = this.dcipher.doFinal(dec);
return new String(utf8, "UTF8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* This method is used to generate the encrypted key.
*/
private void generateKey() {
try {
byte[] decodedStr = new BASE64Decoder().decodeBuffer("rA/LUdBA/hA=");
SecretKey key = new SecretKeySpec(decodedStr, "DES");
this.ecipher = Cipher.getInstance("DES");
this.dcipher = Cipher.getInstance("DES");
this.ecipher.init(1, key);
this.dcipher.init(2, key);
} catch (Exception e) {
e.printStackTrace();
}
}
}
The key present in the class cannot be changed to any other key
in line byte[] decodedStr = new BASE64Decoder().decodeBuffer("rA/LUdBA/hA=");,
and it is giving an exception.
java.security.InvalidKeyException: Invalid key length: 9 bytes
at com.sun.crypto.provider.DESCipher.engineGetKeySize(DashoA13*..)
at javax.crypto.Cipher.b(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
I tried out the below code, and I am getting exactly 8 bytes in the array.
public static void main(String[] args) throws IOException {
byte[] decodedStr = new BASE64Decoder().decodeBuffer("rA/LUdBA/hA=");
for(byte b : decodedStr){
System.out.print(b);
System.out.print(" ");
}
}
}
Any other combination of the key will make the byte array size more than 8 or less than 7.
What is the concept behind getting the byte array size 8 ?
What should be done to use custom key combination or our custom generated keys ?
Please answer Both the questions.
Any other combination of the key will make the byte array size more
than 8 or less than 7.
I doubt that. You're probably adding or removing the wrong characters; or at the wrong position. See: http://en.wikipedia.org/wiki/Base64
And yes 9 bytes is not a valid key length for DES. You could simply shorten it to the proper length. You do get 9 bytes because your base64 string is 3x4 characters long which will be decoded to 3x3 = 9 characters. Trim the output.
What is the concept behind getting the byte array size 8 ?
DES uses 56 Bit Keys. 8 Bytes = 64 Bit, so sufficient bits for the key.
What should be done to use custom key combination or our custom generated keys ?
Let the user enter a key that has at least 7 characters (56 Bit).
I really don't see why you use base64 in this sample at all - probably just because you copied it from somewhere? You just need a few random bytes. The common way to get those are to build a hash from any input the user gives and use bytes from that hash.
if your target is encode and decode the string, Use Base64.
public class PasswordCodecHandler {
Base64 codec = null;
public PasswordCodecHandler() {
codec = new Base64();
}
public String encode(String password) {
byte[] temp;
String encodedPassword = null;
temp = codec.encode(password.getBytes());
encodedPassword = new String(temp);
return encodedPassword;
}
public String decode(byte[] encodedPassword) {
byte[] temp;
String decodedPassword;
temp = codec.decode(encodedPassword);
decodedPassword = new String(temp);
return decodedPassword;
}
public static void main(String[] args) {
PasswordCodecHandler passwordCodecHandler = new PasswordCodecHandler();
String s1 = passwordCodecHandler.encode("password");
System.out.println(s1);
String s2 = passwordCodecHandler.encode("admin");
System.out.println(s2);
String s3 = passwordCodecHandler.encode("administrator");
System.out.println(s3);
String s4 = passwordCodecHandler.encode("123456");
System.out.println(s4);
}
}
For other data type : it can be java.lang.OutOfMemoryError based on the your memory allocation size
/* Download apache common-io.xxx. jar*/
public class CodecHandler {
Base64 codec = null;
public CodecHandler() {
codec = new Base64();
}
public byte[] encode(byte[] decoded) {
return codec.encode(decoded);
}
public byte[] decode(byte[] encoded) {
return codec.decode(encoded);
}
public static void main(String[] args) throws IOException {
File file = new File("D:/Test.mp4");
byte[] fileByteArray = FileUtils.readFileToByteArray(file);
CodecHandler codecHandler = new CodecHandler();
byte[] encoded = codecHandler.encode(fileByteArray);
System.out.println("Byte Size : " + encoded.length);
byte[] decode = codecHandler.decode(encoded);
FileUtils.writeByteArrayToFile(new File("C:/Test.mp4"), decode);
}
}
What is the concept behind getting the byte array size 8?
Your new, based-64–encoded key must be 12 characters long, ending with one, and only one, = character.
In base-64, the character = is a padding character, and it can only appear at the end of the encoded string. Base-64 encoding outputs a block of 4 characters from each input block of 3 bytes. If the input length is not a multiple of 3, the last block is padded.
In the encoded key, "rA/LUdBA/hA=", there are 12 characters, which can encode 9 bytes. But the last character is padding, which means the last byte should be ignored, leaving 8 bytes.
What should be done to use custom key combination or our custom generated keys?
First, you shouldn't use DES. It's too weak and insecure. But in general, the correct process for generating a secure key in Java is to use the KeyGenerator class. For (insecure) DES, you can generate a key and encode it with base-64 like this:
import java.util.Base64;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
…
KeyGenerator gen = KeyGenerator.getInstance("DES");
gen.init(56);
SecretKey secret = gen.generateKey();
String b64 = Base64.getEncoder().encodeToString(secret.getEncoded());
System.out.println(b64);
To use the key, decode it like this:
import java.util.Base64;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeySpec;
…
SecretKey key = new SecretKeySpec(Base64.getDecoder().decode(b64), "DES");
I am currently encoding a password. I have to decode the password. Here is the code to encode. I am trying to get the original password compare it. I have researched about MessageDigest that says it is a one-way method. Not sure how to get the original message. We have a decode method but it isn't giving me the original password - Base64.decode.
public static synchronized String getMD5_Base64(String input) {
if (!isInited) {
isInited = true;
try {
digest = MessageDigest.getInstance("MD5");
} catch (Exception ex) {
}
}
if (digest == null)
return input;
// now everything is ok, go ahead
try {
digest.update(input.getBytes("UTF-8"));
} catch (java.io.UnsupportedEncodingException ex) {
}
byte[] rawData = digest.digest();
byte[] encoded = Base64.encode(rawData);
String retValue = new String(encoded);
return retValue;
}
}
You cannot get the original password. Keep in mind that the digest and Base64 encoding do two completely different things. The MD5 digest creates a cryptographic hash of the data supplied to it. This is irreversible. Base64 is an encoding mechanism to convert data (which might contain unprintable binary data) into a string that is guaranteed to contain only printable characters. This step is reversible.
The standard way of checking a password is not to decode the original password and compare the plain text. What you need to do is take the encoding (MD5 hash then Base64 encode) you did on the original password and apply it to the newly supplied password. Then compare the stored encoded version with the newly encoded version. If they're the same then the passwords matched.
This design is a more secure mechanism than storing passwords that could be decoded. This way, if someone steals your password database they don't automatically have access to all the passwords of your users. In order to break into the system they'd still have to find a password that encoded to the same value. The point of cryptographic hashes like MD5 is to make that very difficult. On the other hand, MD5 is not considered a very secure hash anymore. You'd be better off using SHA1 or SHA256 (but remember, you can't change the existing stored passwords from their MD5 hash to another hash without the original password, which you don't have, i.e. you can't just convert your database of stored passwords).
MessageDigest with MD5 is one way hash. So, why don't you use javax.crypto which can encrypt and decrypt easily. Here is the example:
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import org.apache.commons.codec.binary.Base64;
public class EncryptDecrypt {
private static final String UNICODE_FORMAT = "UTF8";
public static final String DESEDE_ENCRYPTION_SCHEME = "DESede";
private KeySpec ks;
private SecretKeyFactory skf;
private Cipher cipher;
byte[] arrayBytes;
private String myEncryptionKey;
private String myEncryptionScheme;
SecretKey key;
public EncryptDecrypt() throws Exception {
myEncryptionKey = "ThisIsSpartaThisIsSparta";
myEncryptionScheme = DESEDE_ENCRYPTION_SCHEME;
arrayBytes = myEncryptionKey.getBytes(UNICODE_FORMAT);
ks = new DESedeKeySpec(arrayBytes);
skf = SecretKeyFactory.getInstance(myEncryptionScheme);
cipher = Cipher.getInstance(myEncryptionScheme);
key = skf.generateSecret(ks);
}
public String encrypt(String unencryptedString) {
String encryptedString = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plainText = unencryptedString.getBytes(UNICODE_FORMAT);
byte[] encryptedText = cipher.doFinal(plainText);
encryptedString = new String(Base64.encodeBase64(encryptedText));
} catch (Exception e) {
e.printStackTrace();
}
return encryptedString;
}
public String decrypt(String encryptedString) {
String decryptedText=null;
try {
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptedText = Base64.decodeBase64(encryptedString.getBytes());
byte[] plainText = cipher.doFinal(encryptedText);
decryptedText= new String(plainText);
} catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
public static void main(String args []) throws Exception
{
EncryptDecrypt td= new EncryptDecrypt();
String target="password#123";
String encrypted=td.encrypt(target);
String decrypted=td.decrypt(encrypted);
System.out.println("String To Encrypt: "+ target);
System.out.println("Encrypted String: " + encrypted);
System.out.println("Decrypted String: " + decrypted);
}
}
The MD5 hash algorithm is, like all hash algorithms, one-way. The only way to recover the original password is to try every possibility until you get the one whose MD5 hash matches what you received.
If you're trying to compare the contents of the new password with the older passwords you can't use an MD5 hash. As Jherico noted, MD5 (and all hashes) are one-way meaning that you can't get the original text.
In order to do the compare you will have to keep the original value of the password around somewhere. The best way is probably to encrypt (and base64 the result) before storing it to the database. Then in order to do the compare, you decrypt each of the values and do the work that you want
One important note is that storing the user's passwords in any form that can be reversed is can be dangerous if not done properly.