mirror of https://github.com/nextcloud/android
Merge pull request #12723 from nextcloud/bugfix/encrypted-large-file-upload
Bug-fix Encrypted Large File Upload
This commit is contained in:
commit
b7ee06256d
|
@ -36,24 +36,24 @@ import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile;
|
|||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.Encrypted;
|
||||
import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile;
|
||||
import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.lib.resources.e2ee.CsrHelper;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
|
@ -66,6 +66,7 @@ import java.util.Random;
|
|||
import java.util.Set;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
|
||||
|
@ -75,7 +76,6 @@ import static com.owncloud.android.utils.EncryptionUtils.decryptStringAsymmetric
|
|||
import static com.owncloud.android.utils.EncryptionUtils.decryptStringSymmetric;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.deserializeJSON;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.encryptFile;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.generateChecksum;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.generateKey;
|
||||
|
@ -99,6 +99,11 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
|
||||
private static final String MD5_ALGORITHM = "MD5";
|
||||
|
||||
private static final String filename = "ia7OEEEyXMoRa1QWQk8r";
|
||||
private static final String secondFilename = "n9WXAIXO2wRY4R8nXwmo";
|
||||
|
||||
public static final String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
||||
"IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
|
||||
"GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
|
||||
|
@ -395,34 +400,27 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
public void testCryptFileWithoutMetadata() throws Exception {
|
||||
byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
|
||||
byte[] iv = decodeStringToBase64Bytes("gKm3n+mJzeY26q4OfuZEqg==");
|
||||
byte[] authTag = decodeStringToBase64Bytes("PboI9tqHHX3QeAA22PIu4w==");
|
||||
|
||||
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r", "78f42172166f9dc8fd1a7156b1753353", key, iv, authTag));
|
||||
assertTrue(cryptFile(filename, "78f42172166f9dc8fd1a7156b1753353", key, iv));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cryptFileWithMetadata() throws Exception {
|
||||
DecryptedFolderMetadataFileV1 metadata = generateFolderMetadataV1_1();
|
||||
|
||||
// n9WXAIXO2wRY4R8nXwmo
|
||||
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
|
||||
assertTrue(cryptFile(filename,
|
||||
"78f42172166f9dc8fd1a7156b1753353",
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get(filename)
|
||||
.getEncrypted().getKey()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||
.getInitializationVector()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||
.getAuthenticationTag())));
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get(filename)
|
||||
.getInitializationVector())));
|
||||
|
||||
// n9WXAIXO2wRY4R8nXwmo
|
||||
assertTrue(cryptFile("n9WXAIXO2wRY4R8nXwmo",
|
||||
assertTrue(cryptFile(secondFilename,
|
||||
"825143ed1f21ebb0c3b3c3f005b2f5db",
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get(secondFilename)
|
||||
.getEncrypted().getKey()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||
.getInitializationVector()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||
.getAuthenticationTag())));
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get(secondFilename)
|
||||
.getInitializationVector())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -738,8 +736,8 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
DecryptedFolderMetadataFileV1 metadata = new DecryptedFolderMetadataFileV1();
|
||||
String mnemonic = "chimney potato joke science ridge trophy result estate spare vapor much room";
|
||||
|
||||
metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFile());
|
||||
metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFile());
|
||||
metadata.getFiles().put(secondFilename, new DecryptedFile());
|
||||
metadata.getFiles().put(filename, new DecryptedFile());
|
||||
|
||||
String encryptedMetadataKey = "GuFPAULudgD49S4+VDFck3LiqQ8sx4zmbrBtdpCSGcT+T0W0z4F5gYQYPlzTG6WOkdW5LJZK/";
|
||||
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
||||
|
@ -787,9 +785,8 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
// Helper
|
||||
public static boolean compareJsonStrings(String expected, String actual) {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonElement o1 = parser.parse(expected);
|
||||
JsonElement o2 = parser.parse(actual);
|
||||
JsonElement o1 = JsonParser.parseString(expected);
|
||||
JsonElement o2 = JsonParser.parseString(actual);
|
||||
|
||||
if (o1.equals(o2)) {
|
||||
return true;
|
||||
|
@ -828,7 +825,7 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
file1.setMetadataKey(0);
|
||||
file1.setAuthenticationTag("PboI9tqHHX3QeAA22PIu4w==");
|
||||
|
||||
files.put("ia7OEEEyXMoRa1QWQk8r", file1);
|
||||
files.put(filename, file1);
|
||||
|
||||
Data data2 = new Data();
|
||||
data2.setKey("9dfzbIYDt28zTyZfbcll+g==");
|
||||
|
@ -841,70 +838,56 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
file2.setMetadataKey(0);
|
||||
file2.setAuthenticationTag("qOQZdu5soFO77Y7y4rAOVA==");
|
||||
|
||||
files.put("n9WXAIXO2wRY4R8nXwmo", file2);
|
||||
files.put(secondFilename, file2);
|
||||
|
||||
return new DecryptedFolderMetadataFileV1(metadata1, files);
|
||||
}
|
||||
|
||||
|
||||
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
|
||||
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv)
|
||||
throws Exception {
|
||||
File file = getFile(fileName);
|
||||
assertEquals(md5, getMD5Sum(file));
|
||||
File file = File.createTempFile(fileName, "enc");
|
||||
String md5BeforeEncryption = getMD5Sum(file);
|
||||
|
||||
EncryptedFile encryptedFile = encryptFile(file, key, iv);
|
||||
|
||||
File encryptedTempFile = File.createTempFile("file", "tmp");
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
||||
fileOutputStream.write(encryptedFile.getEncryptedBytes());
|
||||
fileOutputStream.close();
|
||||
|
||||
byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.getAuthenticationTag());
|
||||
|
||||
// verify authentication tag
|
||||
assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
|
||||
|
||||
byte[] decryptedBytes = decryptFile(encryptedTempFile,
|
||||
key,
|
||||
iv,
|
||||
authenticationTag,
|
||||
new ArbitraryDataProviderImpl(targetContext),
|
||||
user);
|
||||
// Encryption
|
||||
Cipher encryptorCipher = EncryptionUtils.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||
EncryptionUtils.encryptFile(file, encryptorCipher);
|
||||
String encryptorCipherAuthTag = EncryptionUtils.getAuthenticationTag(encryptorCipher);
|
||||
|
||||
// Decryption
|
||||
Cipher decryptorCipher = EncryptionUtils.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||
File decryptedFile = File.createTempFile("file", "dec");
|
||||
FileOutputStream fileOutputStream1 = new FileOutputStream(decryptedFile);
|
||||
fileOutputStream1.write(decryptedBytes);
|
||||
fileOutputStream1.close();
|
||||
decryptFile(decryptorCipher, file, decryptedFile, encryptorCipherAuthTag, new ArbitraryDataProviderImpl(targetContext), user);
|
||||
|
||||
return md5.compareTo(getMD5Sum(decryptedFile)) == 0;
|
||||
}
|
||||
String md5AfterEncryption = getMD5Sum(decryptedFile);
|
||||
|
||||
private String getMD5Sum(File file) {
|
||||
FileInputStream fileInputStream = null;
|
||||
try {
|
||||
fileInputStream = new FileInputStream(file);
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
byte[] bytes = new byte[2048];
|
||||
int readBytes;
|
||||
|
||||
while ((readBytes = fileInputStream.read(bytes)) != -1) {
|
||||
md5.update(bytes, 0, readBytes);
|
||||
}
|
||||
|
||||
return new String(Hex.encodeHex(md5.digest()));
|
||||
|
||||
} catch (Exception e) {
|
||||
Log_OC.e(this, e.getMessage());
|
||||
} finally {
|
||||
if (fileInputStream != null) {
|
||||
try {
|
||||
fileInputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log_OC.e(this, "Error getting MD5 checksum for file", e);
|
||||
}
|
||||
}
|
||||
if (md5BeforeEncryption == null) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
return "";
|
||||
return md5BeforeEncryption.equals(md5AfterEncryption);
|
||||
}
|
||||
|
||||
public static String getMD5Sum(File file) {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
|
||||
DigestInputStream dis = new DigestInputStream(fis, md);
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = dis.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, bytesRead);
|
||||
}
|
||||
byte[] digest = md.digest();
|
||||
return bytesToHex(digest);
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,4 +21,6 @@
|
|||
*/
|
||||
package com.owncloud.android.datamodel.e2e.v1.encrypted
|
||||
|
||||
class EncryptedFile(var encryptedBytes: ByteArray, var authenticationTag: String)
|
||||
import java.io.File
|
||||
|
||||
class EncryptedFile(var encryptedFile: File, var authenticationTag: String)
|
||||
|
|
|
@ -51,6 +51,8 @@ import java.util.Iterator;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
|
||||
|
||||
/**
|
||||
|
@ -262,31 +264,16 @@ public class DownloadFileOperation extends RemoteOperation {
|
|||
|
||||
byte[] key = decodeStringToBase64Bytes(keyString);
|
||||
byte[] iv = decodeStringToBase64Bytes(nonceString);
|
||||
byte[] authenticationTag = decodeStringToBase64Bytes(authenticationTagString);
|
||||
|
||||
try {
|
||||
byte[] decryptedBytes = EncryptionUtils.decryptFile(tmpFile,
|
||||
key,
|
||||
iv,
|
||||
authenticationTag,
|
||||
new ArbitraryDataProviderImpl(operationContext),
|
||||
user);
|
||||
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(tmpFile)) {
|
||||
fileOutputStream.write(decryptedBytes);
|
||||
}
|
||||
Cipher cipher = EncryptionUtils.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||
EncryptionUtils.decryptFile(cipher, tmpFile, newFile, authenticationTagString, new ArbitraryDataProviderImpl(operationContext), user);
|
||||
} catch (Exception e) {
|
||||
return new RemoteOperationResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadType == DownloadType.DOWNLOAD) {
|
||||
moved = tmpFile.renameTo(newFile);
|
||||
newFile.setLastModified(file.getModificationTimestamp());
|
||||
if (!moved) {
|
||||
result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED);
|
||||
}
|
||||
} else if (downloadType == DownloadType.EXPORT) {
|
||||
if (downloadType == DownloadType.EXPORT) {
|
||||
new FileExportUtils().exportFile(file.getFileName(),
|
||||
file.getMimeType(),
|
||||
operationContext.getContentResolver(),
|
||||
|
|
|
@ -94,6 +94,8 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
@ -558,14 +560,11 @@ public class UploadFileOperation extends SyncOperation {
|
|||
Long creationTimestamp = FileUtil.getCreationTimestamp(originalFile);
|
||||
|
||||
/***** E2E *****/
|
||||
|
||||
// Key, always generate new one
|
||||
byte[] key = EncryptionUtils.generateKey();
|
||||
|
||||
// IV, always generate new one
|
||||
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
|
||||
|
||||
EncryptedFile encryptedFile = EncryptionUtils.encryptFile(mFile, key, iv);
|
||||
Cipher cipher = EncryptionUtils.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||
File file = new File(mFile.getStoragePath());
|
||||
EncryptedFile encryptedFile = EncryptionUtils.encryptFile(file, cipher);
|
||||
|
||||
// new random file name, check if it exists in metadata
|
||||
String encryptedFileName = EncryptionUtils.generateUid();
|
||||
|
@ -580,10 +579,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
}
|
||||
}
|
||||
|
||||
File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
||||
fileOutputStream.write(encryptedFile.getEncryptedBytes());
|
||||
fileOutputStream.close();
|
||||
File encryptedTempFile = encryptedFile.getEncryptedFile();
|
||||
|
||||
/***** E2E *****/
|
||||
|
||||
|
@ -742,6 +738,8 @@ public class UploadFileOperation extends SyncOperation {
|
|||
token = null;
|
||||
}
|
||||
}
|
||||
|
||||
encryptedTempFile.delete();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log_OC.d(TAG, mFile.getStoragePath() + " not exists anymore");
|
||||
|
|
|
@ -22,11 +22,13 @@
|
|||
package com.owncloud.android.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
@ -71,13 +73,17 @@ import org.apache.commons.httpclient.HttpStatus;
|
|||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
|
@ -95,6 +101,7 @@ import java.security.cert.X509Certificate;
|
|||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.InvalidParameterSpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
|
@ -107,6 +114,8 @@ import java.util.UUID;
|
|||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
@ -118,6 +127,7 @@ import javax.crypto.spec.PBEKeySpec;
|
|||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
|
@ -554,95 +564,80 @@ public final class EncryptionUtils {
|
|||
return Base64.decode(string, Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
/*
|
||||
ENCRYPTION
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param ocFile file do crypt
|
||||
* @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
|
||||
* @param iv initialization vector, either from metadata or
|
||||
* {@link EncryptionUtils#randomBytes(int)}
|
||||
* @return encryptedFile with encryptedBytes and authenticationTag
|
||||
*/
|
||||
public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
|
||||
throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
||||
BadPaddingException, IllegalBlockSizeException, IOException {
|
||||
File file = new File(ocFile.getStoragePath());
|
||||
|
||||
return encryptFile(file, encryptionKeyBytes, iv);
|
||||
public static EncryptedFile encryptFile(File file, Cipher cipher) throws InvalidParameterSpecException {
|
||||
File encryptedFile = new File(file.getAbsolutePath() + ".enc");
|
||||
encryptFileWithGivenCipher(file, encryptedFile, cipher);
|
||||
String authenticationTagString = getAuthenticationTag(cipher);
|
||||
return new EncryptedFile(encryptedFile, authenticationTagString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file file do crypt
|
||||
* @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
|
||||
* @param iv initialization vector, either from metadata or
|
||||
* {@link EncryptionUtils#randomBytes(int)}
|
||||
* @return encryptedFile with encryptedBytes and authenticationTag
|
||||
*/
|
||||
public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
|
||||
throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
||||
BadPaddingException, IllegalBlockSizeException, IOException {
|
||||
|
||||
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||
|
||||
Key key = new SecretKeySpec(encryptionKeyBytes, AES);
|
||||
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
|
||||
byte[] fileBytes = new byte[(int) randomAccessFile.length()];
|
||||
randomAccessFile.readFully(fileBytes);
|
||||
|
||||
byte[] cryptedBytes = cipher.doFinal(fileBytes);
|
||||
String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
|
||||
cryptedBytes.length - (128 / 8),
|
||||
cryptedBytes.length));
|
||||
|
||||
return new EncryptedFile(cryptedBytes, authenticationTag);
|
||||
public static String getAuthenticationTag(Cipher cipher) throws InvalidParameterSpecException {
|
||||
byte[] authenticationTag = cipher.getParameters().getParameterSpec(GCMParameterSpec.class).getIV();
|
||||
return encodeBytesToBase64String(authenticationTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file encrypted file
|
||||
* @param encryptionKeyBytes key from metadata
|
||||
* @param iv initialization vector from metadata
|
||||
* @param authenticationTag authenticationTag from metadata
|
||||
* @return decrypted byte[]
|
||||
*/
|
||||
public static byte[] decryptFile(File file,
|
||||
byte[] encryptionKeyBytes,
|
||||
byte[] iv,
|
||||
byte[] authenticationTag,
|
||||
ArbitraryDataProvider arbitraryDataProvider,
|
||||
User user)
|
||||
throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
||||
BadPaddingException, IllegalBlockSizeException, IOException {
|
||||
|
||||
|
||||
public static Cipher getCipher(int mode, byte[] encryptionKeyBytes, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
|
||||
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||
Key key = new SecretKeySpec(encryptionKeyBytes, AES);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
cipher.init(mode, key, spec);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
|
||||
byte[] fileBytes = new byte[(int) randomAccessFile.length()];
|
||||
randomAccessFile.readFully(fileBytes);
|
||||
public static void encryptFileWithGivenCipher(File inputFile, File encryptedFile, Cipher cipher) {
|
||||
try( FileInputStream inputStream = new FileInputStream(inputFile);
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(encryptedFile);
|
||||
CipherOutputStream outputStream = new CipherOutputStream(fileOutputStream, cipher)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
|
||||
// check authentication tag
|
||||
byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
|
||||
fileBytes.length - (128 / 8),
|
||||
fileBytes.length);
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
|
||||
reportE2eError(arbitraryDataProvider, user);
|
||||
throw new SecurityException("Tag not correct");
|
||||
outputStream.close();
|
||||
inputStream.close();
|
||||
|
||||
Log_OC.d(TAG, encryptedFile.getName() + "encrypted successfully");
|
||||
} catch (IOException exception) {
|
||||
Log_OC.d(TAG, "Error caught at encryptFileWithGivenCipher(): " + exception.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return cipher.doFinal(fileBytes);
|
||||
public static void decryptFile(Cipher cipher,
|
||||
File encryptedFile,
|
||||
File decryptedFile,
|
||||
String authenticationTag,
|
||||
ArbitraryDataProvider arbitraryDataProvider,
|
||||
User user) {
|
||||
try (FileInputStream inputStream = new FileInputStream(encryptedFile);
|
||||
FileOutputStream outputStream = new FileOutputStream(decryptedFile)) {
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
byte[] output = cipher.update(buffer, 0, bytesRead);
|
||||
if (output != null) {
|
||||
outputStream.write(output);
|
||||
}
|
||||
}
|
||||
byte[] output = cipher.doFinal();
|
||||
if (output != null) {
|
||||
outputStream.write(output);
|
||||
}
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
if (!getAuthenticationTag(cipher).equals(authenticationTag)) {
|
||||
reportE2eError(arbitraryDataProvider, user);
|
||||
throw new SecurityException("Tag not correct");
|
||||
}
|
||||
|
||||
Log_OC.d(TAG, encryptedFile.getName() + "decrypted successfully");
|
||||
} catch (IOException | BadPaddingException | IllegalBlockSizeException | InvalidParameterSpecException |
|
||||
SecurityException exception) {
|
||||
Log_OC.d(TAG, "Error caught at decryptFile(): " + exception.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -840,7 +835,7 @@ public final class EncryptionUtils {
|
|||
return metadata.getCiphertext();
|
||||
}
|
||||
|
||||
// /**
|
||||
// /**
|
||||
// * Encrypt string with AES/GCM/NoPadding
|
||||
// *
|
||||
// * @param string string to encrypt
|
||||
|
@ -872,22 +867,22 @@ public final class EncryptionUtils {
|
|||
//
|
||||
// return encodedCryptedBytes + delimiter + encodedIV;
|
||||
// }
|
||||
public static String decryptStringSymmetricAsString(String string,
|
||||
byte[] encryptionKeyBytes,
|
||||
byte[] iv,
|
||||
byte[] authenticationTag,
|
||||
ArbitraryDataProvider arbitraryDataProvider,
|
||||
User user
|
||||
) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
|
||||
return decryptStringSymmetricAsString(
|
||||
decodeStringToBase64Bytes(string),
|
||||
encryptionKeyBytes,
|
||||
iv,
|
||||
authenticationTag,
|
||||
false,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
}
|
||||
public static String decryptStringSymmetricAsString(String string,
|
||||
byte[] encryptionKeyBytes,
|
||||
byte[] iv,
|
||||
byte[] authenticationTag,
|
||||
ArbitraryDataProvider arbitraryDataProvider,
|
||||
User user
|
||||
) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
|
||||
return decryptStringSymmetricAsString(
|
||||
decodeStringToBase64Bytes(string),
|
||||
encryptionKeyBytes,
|
||||
iv,
|
||||
authenticationTag,
|
||||
false,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
}
|
||||
|
||||
public static String decryptStringSymmetricAsString(String string,
|
||||
byte[] encryptionKeyBytes,
|
||||
|
@ -1196,7 +1191,7 @@ public static String decryptStringSymmetricAsString(String string,
|
|||
return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
|
||||
+ "\n-----END PRIVATE KEY-----";
|
||||
}
|
||||
|
||||
|
||||
public static PrivateKey PEMtoPrivateKey(String pem) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
byte[] privateKeyBytes = EncryptionUtils.decodeStringToBase64Bytes(pem);
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||
|
|
Loading…
Reference in New Issue