Php 和 Java Sha1 方法给出了不同的结果
Php and Java Sha1 methods gives different results
我正在尝试将 php 代码转换为 java 代码,但是在 sha1 中,我从 php 和 java.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
我也为 java 编写了代码;
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
和Java sha1 方法;
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 "";
}
因此,当我尝试使用像“abcabc”这样的 Utf-8 字符串时,它可以工作,但具有复杂输入的相同代码,如“��8”,给出了两个不同的输出。
Java : b15fe1e7e0abce8284d3695af6c57d7540387ae4,
PHP : 7a59b9888af6355119cf369fd3dbf2810697b98
为什么会这样?
问题出在您的 Java 代码中:而在 PHP 中,字符串只是 字节 的序列(参见 documentation ), 在 Java 中它是 个字符 的序列。 Java 中的字符是 UTF-16 代码点。
byte[]
和 String
之间的所有转换都使用编码,因此您应该尽可能避免此类转换,并在执行时指定编码(使用 String(byte[], Charset)
构造函数或等效的).否则,您最终将使用系统的默认编码(在您的情况下可能是 UTF-8)。
你的情况是:
sPasswordHash
字符串的长度为 23,而原始字节数组有 24 个字节:存在大量编码错误,因此许多字节序列被 replacement character �
、
- 为了补偿你调用
sPassword.substring(19)
(应该是 20),它不包括前 19 个字符。你最终得到的盐略有不同。
你应该在 byte
数组上工作:它不太舒服,但更安全。
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));
(我稍微修改了 sha1
方法以接受字节数组而不是 String
)
备注:由于Java 8 在JRE 中有一个base 64 encoder/decoder (cf. Base64).
我正在尝试将 php 代码转换为 java 代码,但是在 sha1 中,我从 php 和 java.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
我也为 java 编写了代码;
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
和Java sha1 方法;
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 "";
}
因此,当我尝试使用像“abcabc”这样的 Utf-8 字符串时,它可以工作,但具有复杂输入的相同代码,如“��8”,给出了两个不同的输出。
Java : b15fe1e7e0abce8284d3695af6c57d7540387ae4,
PHP : 7a59b9888af6355119cf369fd3dbf2810697b98
为什么会这样?
问题出在您的 Java 代码中:而在 PHP 中,字符串只是 字节 的序列(参见 documentation ), 在 Java 中它是 个字符 的序列。 Java 中的字符是 UTF-16 代码点。
byte[]
和 String
之间的所有转换都使用编码,因此您应该尽可能避免此类转换,并在执行时指定编码(使用 String(byte[], Charset)
构造函数或等效的).否则,您最终将使用系统的默认编码(在您的情况下可能是 UTF-8)。
你的情况是:
sPasswordHash
字符串的长度为 23,而原始字节数组有 24 个字节:存在大量编码错误,因此许多字节序列被 replacement character�
、- 为了补偿你调用
sPassword.substring(19)
(应该是 20),它不包括前 19 个字符。你最终得到的盐略有不同。
你应该在 byte
数组上工作:它不太舒服,但更安全。
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));
(我稍微修改了 sha1
方法以接受字节数组而不是 String
)
备注:由于Java 8 在JRE 中有一个base 64 encoder/decoder (cf. Base64).