Search code examples
javaphpunicodeutf-8decode

Php and Java Sha1 methods gives different results


I am trying to convert a php code to java code but at sha1 , i am getting different strings from php and java.For example,I wrote a code for php;

$password = 'pass123';
$hassedPasswordInDB = 'e1NTSEF9ZWxtNWlJcjJOVkVaenphZjA5dnlnUWFYdVlHaEU3dzQ=';
$sPasswordHash = base64_decode(substr(base64_decode($hassedPasswordInDB), 6));
$sSecretSalt = substr($sPasswordHash, 20); // $sSecretSalt is ��8 here
$sPasswordHash = substr($sPasswordHash, 0, 20);
$hashedPassword = sha1($password.$sSecretSalt); // $hashedPassword is 7a59b9888af6355119cf369fd3dbf2810697b981

And I wrote code for java too ;

    String password = "pass123";
    String hassedPasswordInDB = "e1NTSEF9ZWxtNWlJcjJOVkVaenphZjA5dnlnUWFYdVlHaEU3dzQ=";
    String subFirstDecode = new String(Base64Coder.decode(hassedPasswordInDB)).substring(6);
    String sPasswordHash = new String(Base64Coder.decode(subFirstDecode));
    String secretSalt = sPasswordHash.substring(19); // secretSalt is ��8
    sPasswordHash = sPasswordHash.substring(0, 19);
    String hashedPassword = sha1(password + secretSalt); //hashedPassword is b15fe1e7e0abce8284d3695af6c57d7540387ae4

And Java sha1 method ;

private static String sha1(String input) {
    try {
        // Create MD5 Hash
        MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
        digest.update(input.getBytes());
        byte messageDigest[] = digest.digest();

        // Create Hex String
        StringBuffer hexString = new StringBuffer();
        for (int i = 0; i < messageDigest.length; i++)
            hexString.append(String.format("%02X", 0xFF & messageDigest[i]));
        return hexString.toString().toLowerCase();

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return "";
}

As a result ,when i tried with Utf-8 string like "abcabc" it works but same codes with complicated inputs ,which is like "��8", gives two different outputs.

Java : b15fe1e7e0abce8284d3695af6c57d7540387ae4,
PHP  : 7a59b9888af6355119cf369fd3dbf2810697b98

Why this is happening?


Solution

  • The problem is in your Java code: while in PHP a string is just a sequence of bytes (cf. documentation), in Java it is a sequence of characters. Characters in Java are UTF-16 code points.

    All transformations between byte[] and String use an encoding, so you should avoid such transformations if you can and specify the encoding when you do (use the String(byte[], Charset) constructor or equivalent). Otherwise you'll end up using your system's default encoding (probably UTF-8 in your case).

    What happens in your case is:

    • the sPasswordHash string has length 23, while the original byte array had 24 bytes: there are a lot of encoding errors, so many sequences of bytes are replaced by the replacement character ,
    • to compensate you call sPassword.substring(19) (should be 20), which excludes the first 19 characters. You end up with a slightly different salt.

    You should rather work on byte arrays: it is less comfortable, but safer.

          String password = "pass123";
          String hashedPasswordInDB = "e1NTSEF9ZWxtNWlJcjJOVkVaenphZjA5dnlnUWFYdVlHaEU3dzQ=";
          // This should be ASCII
          final String firstDecode = new String(Base64.getDecoder().decode(hashedPasswordInDB), StandardCharsets.US_ASCII);
          final byte[] hashAndSalt = Base64.getDecoder().decode(firstDecode.substring("{SSHA}".length()));
          final byte[] passwordHash = Arrays.copyOf(hashAndSalt, 20);
          final byte[] salt = Arrays.copyOfRange(hashAndSalt, 20, hashAndSalt.length);
          final byte[] passwordBytes = password.getBytes();
          final byte[] passwordAndSalt = Arrays.copyOf(passwordBytes, passwordBytes.length + salt.length);
          System.arraycopy(salt, 0, passwordAndSalt, passwordBytes.length, salt.length);
          System.out.println(sha1(passwordAndSalt));
    

    (I slightly modified the sha1 method to accept a byte array instead of String)

    Remark: Since Java 8 there is a base 64 encoder/decoder in the JRE (cf. Base64).