我在php中使用AES加密对文件进行了加密,代码如下。
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
openssl_encrypt($contenuto, $ALGORITHM, $password, 0, $IV);
现在我正试图在Android中解密它,但我总是面临InvalidKeyException:密钥长度不是128/192/256位错误。这是安卓代码:
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解码,这是必要的,因为PHP代码的密文是Base64编码的。
Base64解码可以使用Apache Commons编解码器的Base64InputStream
类轻松实现,该类在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);
}
}
}
其他修复错误/优化点是(另请参阅其他答案/评论(:
- 使用类似于PHP代码的CBC模式
- 使用PHP代码中的密钥
- 所用编码的明确说明
编辑:考虑静脉注射,请参阅@Michael Fehr的评论。
通常为每个加密生成一个新的随机IV。IV不是秘密的,通常放在密文之前,结果是Base64编码的。接受者可以将两个部分分开,因为IV的大小是已知的(对应于块大小(。这个构造也可以与Base64InputStream
类结合使用,其中IV必须在Base64InputStream
实例化和Cipher
实例化/初始化之间确定:
...
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的评论(,则必须在FileInputStream
和Base64InputStream
实例化之间确定IV(分隔符也必须刷新(。
以下完整的工作示例展示了如何处理密码问题和进行Base64解码,这些示例只使用字符串而不是文件。请记住@Topaco所说的openssl以Base64编码对输出进行编码,该编码需要转换为字节格式,然后文件才能与CipherInputStream一起使用进行解密!
第三点(实际上并不是一个bug(是,在Java/Android端,您没有为从字符串到字节数组的转换设置字符集——只需添加StandardCharsets.UTF_8,您就可以接受这一点。
注意没有正确的异常处理
这是我的示例PHP代码:
<?php
// https://stackoverflow.com/questions/63113746/decrypt-file-using-aes-method-in-android
$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("https://stackoverflow.com/questions/63113746/decrypt-file-using-aes-method-in-android");
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
在Michael的回答的帮助下,我写下了成功解密文件的代码。
在应用程序级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();
}
我希望这会有所帮助。
答案不错,但想加上我关于Usually a new random IV is generated for each encryption
的2美分。将通常更改为需要或必须。我不知道为什么每次都不使用新的静脉输液。如果您没有并且碰巧使用相同的密钥,那么每次都会将相同的数据加密到相同的输出。这意味着攻击者可以通过比较加密的值来判断解密的值是否相同。加密保护原始数据不受窥探,您仍然必须确保他们也不能确定有关原始数据的任何信息。如果你对足够多的数据进行加密,对每条记录使用相同的密钥和IV,黑客就可以开始攻击你的系统。