The approach itself is good; SHA-256 by itself is a strong, one-way hashing function. It cannot be "decrypted". But it's fast, thus allowing rapid brute-forcing of the password using a dictionary.
For better security you can slow things down with e.g. bcrypt or PBKDF2. Some 100ms will not be noticeable by the user, but makes brute-forcing impractical.
Here's an example with PBKDF2 using 100000 iterations of SHA-256. It also uses a random salt.
SecureRandom random = SecureRandom.getInstanceStrong(); byte[] salt = new byte[16]; random.nextBytes(salt); KeySpec spec = new PBEKeySpec("my-secret-password".toCharArray(), salt, 100000, 256); SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] hash = f.generateSecret(spec).getEncoded(); Base64.Encoder enc = Base64.getEncoder(); System.out.printf("salt: %s%n", enc.encodeToString(salt)); System.out.printf("hash: %s%n", enc.encodeToString(hash));
Note: PBKDF2WithHmacSHA256 is available since Java 8.
Here's a more complete example:
private static final SecureRandom random = new SecureRandom(); /** * One-way encrypts (hashes) the given password. * * @param saltpw the salt (will be generated when null) * @param pw the password to encrypt * @return encrypted salted password */ public static String encrypt(String saltpw, String pw) throws GeneralSecurityException { byte[] salt; if (saltpw == null) { salt = new byte[16]; random.nextBytes(salt); } else { salt = Base64.getDecoder().decode(saltpw.replaceFirst("\\$.*", "")); } KeySpec spec = new PBEKeySpec(pw.toCharArray(), salt, 100000, 256); SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] hash = f.generateSecret(spec).getEncoded(); Base64.Encoder enc = Base64.getEncoder(); return enc.encodeToString(salt) + "$" + enc.encodeToString(hash); } public static void main(String[] args) throws Exception { String enc = encrypt(null, "my-secret-password"); System.out.printf("enc : %s\n", enc); String test1 = encrypt(enc, "my-secret-password"); System.out.printf("test 1: %s, valid: %b\n", test1, enc.equals(test1)); String test2 = encrypt(enc, "some-other-password"); System.out.printf("test 2: %s, valid: %b\n", test2, enc.equals(test2)); }
Prints:
enc : B5V6SjkjJpeOxvMAkPf7EA==$NNDA7o+Dpd+M+H99WVxY0B8adqVWJHZ+HIjgPxMljwo= test 1: B5V6SjkjJpeOxvMAkPf7EA==$NNDA7o+Dpd+M+H99WVxY0B8adqVWJHZ+HIjgPxMljwo=, valid: true test 2: B5V6SjkjJpeOxvMAkPf7EA==$4H1SpH8N+/jqU40G6RWb+ReHUB3C58iAaU4l39j+TV8=, valid: false
Notice how test 1 results in exactly the same encrypted string as the original password, and that test 2 (with a wrong password) doesn't. So that's how you can verify that the provided password is valid or not, by just comparing the hashes.