Adapting the Java implementation you linked to in a comment, which is close but isn't quite using the salt properly:
import java.security.MessageDigest;
import java.util.Formatter;
class Main{
public static String calculateHash(String password) throws Exception{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
String encodedPassword = "S:71752CE0530476A8B2E0DD218AE59CB71B211D7E1DB70EE23BFB23BDFD48";
// Convert password to bytes
byte[] bPassword = password.getBytes("UTF-8");
// Get salt from encoded password
String salt = encodedPassword.substring(42, 62);
System.out.println("Salt is " + salt);
// Convert salt from hex back to bytes
// based on http://stackoverflow.com/a/140861/266304
int len = salt.length();
byte[] bSalt = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
bSalt[i / 2] = (byte) ((Character.digit(salt.charAt(i), 16) << 4)
+ Character.digit(salt.charAt(i+1), 16));
}
// Add converted salt to password bytes
// based on http://stackoverflow.com/a/80503/266304
byte[] bData = new byte[bPassword.length + bSalt.length];
System.arraycopy(bPassword, 0, bData, 0, bPassword.length);
System.arraycopy(bSalt, 0, bData, bPassword.length, bSalt.length);
// Hash the final byte array
crypt.update(bData);
byte bHash[] = crypt.digest();
Formatter formatter = new Formatter();
for (byte b : bHash)
{
formatter.format("%02x", b);
}
System.out.println("Expected " + encodedPassword.substring(2,42));
return formatter.toString().toUpperCase();
}
public static void main(String[] args) throws Exception {
System.out.println("The result is " + calculateHash("ZK3002"));
}
}
Which gives output:
Salt is 1DB70EE23BFB23BDFD48
Expected 71752CE0530476A8B2E0DD218AE59CB71B211D7E
The result is 71752CE0530476A8B2E0DD218AE59CB71B211D7E
The PL/SQL version involves some conversion; dbms_crypto.hash()
takes a RAW
argument, so you have to convert the plain-text password to RAW
, then concatenate the extracted salt - which is already hex. (In the PL/SQL version in Pete Finnigan's blog you may notice that he has an explicit hextoraw
call, so I'm simplifying a bit). So the argument passed to dbms_crypto.hash
for your example would be the hex (OK, raw) equivalent of ZK3002
, which is 5A4B33303032
, with the hex salt concatenated to that; so 5A4B333030321DB70EE23BFB23BDFD48
.
For the Java version you pass a byte array, but that means you need to convert the salt extracted from the stored password back from hex before tacking it on to the password; and since it's unlikely to have a useful string representation you might as well put it straight into a byte array. So, convert the password to a byte array, convert the salt into a byte array, and stick the two arrays together. That then becomes the value you pass to MessageDigest
.
You can compare the hash this produces with the Oracle-hashed version, skipping the initial S:
and the embedded salt.