使用盐将 Rijndael 解密从 PHP 移植到 Java
Porting Rijndael decryption from PHP to Java with salt
我在将带有解密示例的 php 代码转换为 java 时遇到问题。
这是 php 代码:
function decrypt($encrypted, $password, $salt='2#g+XK^Sc3"4ABXbvwF8CPD%en%;9,c(') {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Retrieve $iv which is the first 22 characters plus ==, base64_decoded.
$iv = base64_decode(substr($encrypted, 0, 22) . '==');
// Remove $iv from $encrypted.
$encrypted = substr($encrypted, 22);
// Decrypt the data. rtrim won't corrupt the data because the last 32 characters are the md5 hash; thus any [=12=] character has to be padding.
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "[=12=]");
// Retrieve $hash which is the last 32 characters of $decrypted.
$hash = substr($decrypted, -32);
// Remove the last 32 characters from $decrypted.
$decrypted = substr($decrypted, 0, -32);
// Integrity check. If this fails, either the data is corrupted, or the password/salt was incorrect.
if (md5($decrypted) != $hash) return false;
// Yay!
return $decrypted;
}
这是我的 java 代码,我已经完成了。但这是行不通的。
private static String password = "AxkbK2jZ5PMaeNZWfn8XRLUWF2waGwH2EkAXxBDU6aZ";
private static String salt = "2#g+XK^Sc3\"4ABXbvwF8CPD%en%;9,c(";
private static String text = "Fm+Zfufqe3DjRQtWcYdw9g9oXriDjrAkRrBLhEfu7fCtT4BzD0gw7D+8KxrcbbgJm26peTUWHU2k4YJ4KqCSRQN3NPzuXwlJ4mC4444Edg3Q==";
public String decrypt(String pass, String encr) {
try {
int i = 0;
String key = hash();
byte[] iv = Base64.decodeBase64(text.substring(0, 22) + "==");
Cipher cipher = Cipher.getInstance("DES");
SecretKeySpec keySpec = new SecretKeySpec(password.getBytes(), "DES");
IvParameterSpec ivSpec = new IvParameterSpec(salt.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
ByteArrayInputStream fis = new ByteArrayInputStream(iv);
CipherInputStream cis = new CipherInputStream(fis, cipher);
ByteArrayOutputStream fos = new ByteArrayOutputStream();
// decrypting
byte[] b = new byte[8];
while ((i = cis.read(b)) != -1) {
fos.write(b, 0, i);
}
fos.flush();
fos.close();
cis.close();
fis.close();
return fos.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String hash() {
StringBuffer sb = new StringBuffer();
try {
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-256");
md.update((password + salt).getBytes());
byte byteData[] = md.digest();
for (int i = 0; i < byteData.length; i++) {
sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} finally {
return sb.toString();
}
}
您的代码存在一些问题。
MCRYPT_RIJNDAEL_128
是 AES 而不是 DES。这是两种完全不同的算法。
Cipher.getInstance("DES");
可能默认为 Cipher.getInstance("DES/ECB/PKCS5Padding");
,具体取决于您的默认 JCE 提供程序
- ECB 和 CBC 完全不同modes of operation。
- PHP 用 0x00 字节填充明文,而 PKCS#5/PKCS#7 padding adds padding bytes which contain the number of added padding bytes(比较方法 1 和 4)。
- 您需要使用
Cipher.getInstance("AES/CBC/NoPadding");
并自行删除尾部 0x00 和 0x04 字节。
- 您没有使用您生成的
key
,而是直接使用不是密钥的密码。此外,您的密钥不应采用十六进制编码,因为您还在 PHP. 中使用了原始密钥
- 盐不是IV。
- 您没有从恢复的明文末尾切下哈希来验证它。
您的原始 PHP 代码中有些令人畏惧的东西:
- 如今,SHA-256 的单次传递还不够好。您的密码需要非常长(50 多个随机字符)以确保安全。使用具有高成本或迭代的 PBKDF2、bcrypt、scrypt 以便能够使用更短的密码。
- 似乎 IV 实际上是附加到 Base64 编码的密文,就像没有填充字节的 Base64 编码的字符串一样。这非常不寻常,可能会导致误解。
- 使用 MCrypt 的零填充而不是利用 PKCS#7 padding。
- 检查明文的完整性很好,但你应该检查密文的完整性,因为 encrypt-then-MAC 通常更好。 MD5 也不够好。您应该至少使用 HMAC-SHA256。
我在将带有解密示例的 php 代码转换为 java 时遇到问题。 这是 php 代码:
function decrypt($encrypted, $password, $salt='2#g+XK^Sc3"4ABXbvwF8CPD%en%;9,c(') {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Retrieve $iv which is the first 22 characters plus ==, base64_decoded.
$iv = base64_decode(substr($encrypted, 0, 22) . '==');
// Remove $iv from $encrypted.
$encrypted = substr($encrypted, 22);
// Decrypt the data. rtrim won't corrupt the data because the last 32 characters are the md5 hash; thus any [=12=] character has to be padding.
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "[=12=]");
// Retrieve $hash which is the last 32 characters of $decrypted.
$hash = substr($decrypted, -32);
// Remove the last 32 characters from $decrypted.
$decrypted = substr($decrypted, 0, -32);
// Integrity check. If this fails, either the data is corrupted, or the password/salt was incorrect.
if (md5($decrypted) != $hash) return false;
// Yay!
return $decrypted;
}
这是我的 java 代码,我已经完成了。但这是行不通的。
private static String password = "AxkbK2jZ5PMaeNZWfn8XRLUWF2waGwH2EkAXxBDU6aZ";
private static String salt = "2#g+XK^Sc3\"4ABXbvwF8CPD%en%;9,c(";
private static String text = "Fm+Zfufqe3DjRQtWcYdw9g9oXriDjrAkRrBLhEfu7fCtT4BzD0gw7D+8KxrcbbgJm26peTUWHU2k4YJ4KqCSRQN3NPzuXwlJ4mC4444Edg3Q==";
public String decrypt(String pass, String encr) {
try {
int i = 0;
String key = hash();
byte[] iv = Base64.decodeBase64(text.substring(0, 22) + "==");
Cipher cipher = Cipher.getInstance("DES");
SecretKeySpec keySpec = new SecretKeySpec(password.getBytes(), "DES");
IvParameterSpec ivSpec = new IvParameterSpec(salt.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
ByteArrayInputStream fis = new ByteArrayInputStream(iv);
CipherInputStream cis = new CipherInputStream(fis, cipher);
ByteArrayOutputStream fos = new ByteArrayOutputStream();
// decrypting
byte[] b = new byte[8];
while ((i = cis.read(b)) != -1) {
fos.write(b, 0, i);
}
fos.flush();
fos.close();
cis.close();
fis.close();
return fos.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String hash() {
StringBuffer sb = new StringBuffer();
try {
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-256");
md.update((password + salt).getBytes());
byte byteData[] = md.digest();
for (int i = 0; i < byteData.length; i++) {
sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} finally {
return sb.toString();
}
}
您的代码存在一些问题。
MCRYPT_RIJNDAEL_128
是 AES 而不是 DES。这是两种完全不同的算法。Cipher.getInstance("DES");
可能默认为Cipher.getInstance("DES/ECB/PKCS5Padding");
,具体取决于您的默认 JCE 提供程序- ECB 和 CBC 完全不同modes of operation。
- PHP 用 0x00 字节填充明文,而 PKCS#5/PKCS#7 padding adds padding bytes which contain the number of added padding bytes(比较方法 1 和 4)。
- 您需要使用
Cipher.getInstance("AES/CBC/NoPadding");
并自行删除尾部 0x00 和 0x04 字节。
- 您没有使用您生成的
key
,而是直接使用不是密钥的密码。此外,您的密钥不应采用十六进制编码,因为您还在 PHP. 中使用了原始密钥
- 盐不是IV。
- 您没有从恢复的明文末尾切下哈希来验证它。
您的原始 PHP 代码中有些令人畏惧的东西:
- 如今,SHA-256 的单次传递还不够好。您的密码需要非常长(50 多个随机字符)以确保安全。使用具有高成本或迭代的 PBKDF2、bcrypt、scrypt 以便能够使用更短的密码。
- 似乎 IV 实际上是附加到 Base64 编码的密文,就像没有填充字节的 Base64 编码的字符串一样。这非常不寻常,可能会导致误解。
- 使用 MCrypt 的零填充而不是利用 PKCS#7 padding。
- 检查明文的完整性很好,但你应该检查密文的完整性,因为 encrypt-then-MAC 通常更好。 MD5 也不够好。您应该至少使用 HMAC-SHA256。