在 Android 中使用 AES 方法解密文件
Decrypt file using AES method in Android
我在 php 中使用以下代码使用 AES 加密对文件进行了加密。
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
openssl_encrypt($contenuto, $ALGORITHM, $password, 0, $IV);
现在我试图在 Android 中解密它,但我总是遇到 InvalidKeyException: Key length not 128/192/256 bits 错误。这是 android 代码:
String initializationVector = "12dasdq3g5b2434b";
String password = "123";
FileInputStream fis = new FileInputStream(cryptFilepath);
FileOutputStream fos = new FileOutputStream(outputFilePath);
byte[] key = (password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key,16);
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(initializationVector.getBytes()));
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[16];
while((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
任何人都可以建议我该怎么做。任何帮助将不胜感激。
以下完整的工作示例展示了如何处理密码问题和进行 Base64 解码,这些示例只使用字符串而不是文件。请记住@Topaco 所说的 openssl 在 Base64 编码中对输出进行编码,在文件可以与 CipherInputStream 一起用于解密之前需要将其转换为字节格式!
第三点(不是真正的错误)是在 Java/Android 方面你没有为从字符串到字节数组的转换设置字符集 - 只需添加 StandardCharsets.UTF_8 和你没意见。
注意没有正确的异常处理!
这是我的示例PHP-代码:
<?php
//
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
$plaintext = "my content to encrypt";
echo 'plaintext: ' . $plaintext . PHP_EOL;
$ciphertext = openssl_encrypt($plaintext, $ALGORITHM, $password, 0, $IV);
echo 'ciphertext: ' . $ciphertext . PHP_EOL;
$decryptedtext = openssl_decrypt($ciphertext, $ALGORITHM, $password, 0, $IV);
echo 'decryptedtext: ' . $decryptedtext . PHP_EOL;
?>
PHP 端的输出:
plaintext: my content to encrypt
ciphertext: DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4=
decryptedtext: my content to encrypt
样本Java-代码:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class DecryptInJava {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
System.out.println("");
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
String ciphertext = "DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4="; // password 123 in openssl
// openssl encodes the output in base64 encoding, so first we have to decode it
byte[] ciphertextByte = Base64.getDecoder().decode(ciphertext);
// creating a key filled with 16 'x0'
byte[] key = new byte[16]; // 16 bytes for aes cbc 128
// copying the password to the key, leaving the x0 at the end
System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
// don't use just AES because that defaults to AES/ECB...
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtext = cipher.doFinal(ciphertextByte);
System.out.println("decryptedtext: " + new String(decryptedtext));
}
}
Java 端的输出:
decryptedtext: my content to encrypt
在的帮助下,我写下了成功解密文件的代码。
在您的应用级别 build.gradle 文件中添加以下依赖项:
implementation 'commons-io:commons-io:2.7'
implementation 'commons-codec:commons-codec:1.13'
Java代码:
public static void decrypt(String path, String outPath) throws Exception {
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
byte[] key = new byte[16]; // 16 bytes for aes cbc 128
System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] input_file;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
input_file = Files.readAllBytes(Paths.get(path));
} else {
input_file = org.apache.commons.io.FileUtils.readFileToByteArray(new File(path));
}
byte[] decodedBytes;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
decodedBytes = Base64.getDecoder().decode(input_file);
} else {
decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(input_file);
}
byte[] decryptedtext = cipher.doFinal(decodedBytes);
FileOutputStream fos = new FileOutputStream(outPath);
fos.write(decryptedtext);
fos.flush();
fos.close();
}
希望对您有所帮助。
问题中发布的原始代码使用流来读取、解密和写入相应的文件。这使得处理大于可用内存的文件成为可能。
然而,最初发布的代码缺少Base64解码,这是必需的,因为PHP代码的密文是Base64编码的。
Base64 解码可以使用 Apache Commons Codec 的 Base64InputStream
class 轻松实现,它在 FileInputStream
和 CipherInputStream
之间运行,因此易于集成:
import org.apache.commons.codec.binary.Base64InputStream;
...
public static void decrypt(String ciphertextFilepath, String decryptedFilePath) throws Exception {
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
byte[] key = new byte[16];
byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
System.arraycopy(passwordBytes, 0, key, 0, passwordBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
Base64InputStream b64is = new Base64InputStream(fis);
CipherInputStream cis = new CipherInputStream(b64is, cipher);
FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
int read;
byte[] buffer = new byte[16]; // 16 bytes for testing, in practice use a suitable size (depending on your RAM size), e.g. 64 Mi
while((read = cis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
}
}
其他修复的错误/优化点是(另见其他答案/评论):
- CBC模式的使用类似于PHP代码
- 使用 PHP 代码中的密钥
- 所用编码的明确规范
编辑: IV 的考虑,请参阅@Michael Fehr 的评论。
通常每次加密都会生成一个新的随机IV。 IV 不是秘密的,通常放在密文之前,结果是 Base64 编码的。接收者可以将两个部分分开,因为 IV 的大小是已知的(对应于块大小)。此构造也可以与 Base64InputStream
class 结合使用,其中 IV 必须在 Base64InputStream
实例化和 Cipher
instantiation/initialization 之间确定:
...
try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
Base64InputStream b64is = new Base64InputStream(fis)){
byte[] iv = b64is.readNBytes(16); // 16 bytes for AES
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
try (CipherInputStream cis = new CipherInputStream(b64is, cipher);
FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
...
如果在加密过程中 IV 和密文是 Base64 编码分别然后连接起来,由分隔符分隔(参见@Michael Fehr 的评论),则 IV 的确定必须在两者之间完成FileInputStream
和 Base64InputStream
实例化(分隔符也必须被刷新)。
很好的答案,但想补充我关于 Usually a new random IV is generated for each encryption
的 2 美分。将通常更改为需要或必须。我不知道有什么理由不每次都使用新的 IV。如果您不这样做并且碰巧使用相同的密钥,则相同的数据每次都会加密为相同的输出。这意味着攻击者可以通过比较加密值来判断解密值是否相同。加密可以保护原始数据免遭窥探,您仍然必须确保他们也无法确定有关原始数据的任何信息。如果您加密了足够多的数据,对每条记录使用相同的密钥和 IV,那么黑客就有机会开始攻击您的系统。
我在 php 中使用以下代码使用 AES 加密对文件进行了加密。
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
openssl_encrypt($contenuto, $ALGORITHM, $password, 0, $IV);
现在我试图在 Android 中解密它,但我总是遇到 InvalidKeyException: Key length not 128/192/256 bits 错误。这是 android 代码:
String initializationVector = "12dasdq3g5b2434b";
String password = "123";
FileInputStream fis = new FileInputStream(cryptFilepath);
FileOutputStream fos = new FileOutputStream(outputFilePath);
byte[] key = (password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key,16);
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(initializationVector.getBytes()));
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[16];
while((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
任何人都可以建议我该怎么做。任何帮助将不胜感激。
以下完整的工作示例展示了如何处理密码问题和进行 Base64 解码,这些示例只使用字符串而不是文件。请记住@Topaco 所说的 openssl 在 Base64 编码中对输出进行编码,在文件可以与 CipherInputStream 一起用于解密之前需要将其转换为字节格式!
第三点(不是真正的错误)是在 Java/Android 方面你没有为从字符串到字节数组的转换设置字符集 - 只需添加 StandardCharsets.UTF_8 和你没意见。
注意没有正确的异常处理!
这是我的示例PHP-代码:
<?php
//
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
$plaintext = "my content to encrypt";
echo 'plaintext: ' . $plaintext . PHP_EOL;
$ciphertext = openssl_encrypt($plaintext, $ALGORITHM, $password, 0, $IV);
echo 'ciphertext: ' . $ciphertext . PHP_EOL;
$decryptedtext = openssl_decrypt($ciphertext, $ALGORITHM, $password, 0, $IV);
echo 'decryptedtext: ' . $decryptedtext . PHP_EOL;
?>
PHP 端的输出:
plaintext: my content to encrypt
ciphertext: DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4=
decryptedtext: my content to encrypt
样本Java-代码:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class DecryptInJava {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
System.out.println("");
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
String ciphertext = "DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4="; // password 123 in openssl
// openssl encodes the output in base64 encoding, so first we have to decode it
byte[] ciphertextByte = Base64.getDecoder().decode(ciphertext);
// creating a key filled with 16 'x0'
byte[] key = new byte[16]; // 16 bytes for aes cbc 128
// copying the password to the key, leaving the x0 at the end
System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
// don't use just AES because that defaults to AES/ECB...
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtext = cipher.doFinal(ciphertextByte);
System.out.println("decryptedtext: " + new String(decryptedtext));
}
}
Java 端的输出:
decryptedtext: my content to encrypt
在
在您的应用级别 build.gradle 文件中添加以下依赖项:
implementation 'commons-io:commons-io:2.7'
implementation 'commons-codec:commons-codec:1.13'
Java代码:
public static void decrypt(String path, String outPath) throws Exception {
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
byte[] key = new byte[16]; // 16 bytes for aes cbc 128
System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] input_file;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
input_file = Files.readAllBytes(Paths.get(path));
} else {
input_file = org.apache.commons.io.FileUtils.readFileToByteArray(new File(path));
}
byte[] decodedBytes;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
decodedBytes = Base64.getDecoder().decode(input_file);
} else {
decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(input_file);
}
byte[] decryptedtext = cipher.doFinal(decodedBytes);
FileOutputStream fos = new FileOutputStream(outPath);
fos.write(decryptedtext);
fos.flush();
fos.close();
}
希望对您有所帮助。
问题中发布的原始代码使用流来读取、解密和写入相应的文件。这使得处理大于可用内存的文件成为可能。
然而,最初发布的代码缺少Base64解码,这是必需的,因为PHP代码的密文是Base64编码的。
Base64 解码可以使用 Apache Commons Codec 的 Base64InputStream
class 轻松实现,它在 FileInputStream
和 CipherInputStream
之间运行,因此易于集成:
import org.apache.commons.codec.binary.Base64InputStream;
...
public static void decrypt(String ciphertextFilepath, String decryptedFilePath) throws Exception {
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
byte[] key = new byte[16];
byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
System.arraycopy(passwordBytes, 0, key, 0, passwordBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
Base64InputStream b64is = new Base64InputStream(fis);
CipherInputStream cis = new CipherInputStream(b64is, cipher);
FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
int read;
byte[] buffer = new byte[16]; // 16 bytes for testing, in practice use a suitable size (depending on your RAM size), e.g. 64 Mi
while((read = cis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
}
}
其他修复的错误/优化点是(另见其他答案/评论):
- CBC模式的使用类似于PHP代码
- 使用 PHP 代码中的密钥
- 所用编码的明确规范
编辑: IV 的考虑,请参阅@Michael Fehr 的评论。
通常每次加密都会生成一个新的随机IV。 IV 不是秘密的,通常放在密文之前,结果是 Base64 编码的。接收者可以将两个部分分开,因为 IV 的大小是已知的(对应于块大小)。此构造也可以与 Base64InputStream
class 结合使用,其中 IV 必须在 Base64InputStream
实例化和 Cipher
instantiation/initialization 之间确定:
...
try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
Base64InputStream b64is = new Base64InputStream(fis)){
byte[] iv = b64is.readNBytes(16); // 16 bytes for AES
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
try (CipherInputStream cis = new CipherInputStream(b64is, cipher);
FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
...
如果在加密过程中 IV 和密文是 Base64 编码分别然后连接起来,由分隔符分隔(参见@Michael Fehr 的评论),则 IV 的确定必须在两者之间完成FileInputStream
和 Base64InputStream
实例化(分隔符也必须被刷新)。
很好的答案,但想补充我关于 Usually a new random IV is generated for each encryption
的 2 美分。将通常更改为需要或必须。我不知道有什么理由不每次都使用新的 IV。如果您不这样做并且碰巧使用相同的密钥,则相同的数据每次都会加密为相同的输出。这意味着攻击者可以通过比较加密值来判断解密值是否相同。加密可以保护原始数据免遭窥探,您仍然必须确保他们也无法确定有关原始数据的任何信息。如果您加密了足够多的数据,对每条记录使用相同的密钥和 IV,那么黑客就有机会开始攻击您的系统。