/*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2017 Tobias Kaminsky
* Copyright (C) 2017 Nextcloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.owncloud.android.utils;
import android.accounts.Account;
import android.content.Context;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Pair;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
import com.owncloud.android.operations.UploadException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.httpclient.HttpStatus;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Utils for encryption
*/
public final class EncryptionUtils {
private static String TAG = EncryptionUtils.class.getSimpleName();
public static final String PUBLIC_KEY = "PUBLIC_KEY";
public static final String PRIVATE_KEY = "PRIVATE_KEY";
public static final String MNEMONIC = "MNEMONIC";
public static final int ivLength = 16;
public static final int saltLength = 40;
public static final String ivDelimiter = "|"; // not base64 encoded
public static final String ivDelimiterOld = "fA=="; // "|" base64 encoded
private static final String HASH_DELIMITER = "$";
private static final int iterationCount = 1024;
private static final int keyStrength = 256;
private static final String AES_CIPHER = "AES/GCM/NoPadding";
private static final String AES = "AES";
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final String RSA = "RSA";
private EncryptionUtils() {
// utility class -> private constructor
}
/*
JSON
*/
public static T deserializeJSON(String json, TypeToken type) {
return new Gson().fromJson(json, type.getType());
}
public static String serializeJSON(Object data) {
return new Gson().toJson(data);
}
/*
METADATA
*/
/**
* Encrypt folder metaData
*
* @param decryptedFolderMetadata folder metaData to encrypt
* @return EncryptedFolderMetadata encrypted folder metadata
*/
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
String privateKey)
throws NoSuchAlgorithmException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException {
HashMap files = new HashMap<>();
EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
.getMetadata(), files);
// Encrypt each file in "files"
for (Map.Entry entry : decryptedFolderMetadata
.getFiles().entrySet()) {
String key = entry.getKey();
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric(
decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()),
privateKey));
// encrypt
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, decryptedMetadataKey));
files.put(key, encryptedFile);
}
return encryptedFolderMetadata;
}
/*
* decrypt folder metaData with private key
*/
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
String privateKey)
throws NoSuchAlgorithmException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException {
HashMap files = new HashMap<>();
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
encryptedFolderMetadata.getMetadata(), files);
for (Map.Entry entry : encryptedFolderMetadata
.getFiles().entrySet()) {
String key = entry.getKey();
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
.getMetadataKeys().get(encryptedFile.getMetadataKey()), privateKey));
// decrypt
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
new TypeToken() {
}));
files.put(key, decryptedFile);
}
return decryptedFolderMetadata;
}
/**
* Download metadata for folder and decrypt it
*
* @return decrypted metadata or null
*/
public static @Nullable
DecryptedFolderMetadata downloadFolderMetadata(OCFile folder, OwnCloudClient client,
Context context, Account account) {
RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId())
.execute(client);
if (!getMetadataOperationResult.isSuccess()) {
return null;
}
// decrypt metadata
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
serializedEncryptedMetadata, new TypeToken() {
});
try {
return EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
} catch (Exception e) {
Log_OC.e(TAG, e.getMessage());
return null;
}
}
/*
BASE 64
*/
public static byte[] encodeStringToBase64Bytes(String string) {
try {
return Base64.encode(string.getBytes(), Base64.NO_WRAP);
} catch (Exception e) {
return new byte[0];
}
}
public static String decodeBase64BytesToString(byte[] bytes) {
try {
return new String(Base64.decode(bytes, Base64.NO_WRAP));
} catch (Exception e) {
return "";
}
}
public static String encodeBytesToBase64String(byte[] bytes) {
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
public static byte[] decodeStringToBase64Bytes(String string) {
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);
}
/**
* @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);
}
/**
* @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)
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.DECRYPT_MODE, key, spec);
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
byte[] fileBytes = new byte[(int) randomAccessFile.length()];
randomAccessFile.readFully(fileBytes);
// check authentication tag
byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
fileBytes.length - (128 / 8), fileBytes.length);
if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
throw new SecurityException("Tag not correct");
}
return cipher.doFinal(fileBytes);
}
public static class EncryptedFile {
public byte[] encryptedBytes;
public String authenticationTag;
public EncryptedFile(byte[] encryptedBytes, String authenticationTag) {
this.encryptedBytes = encryptedBytes;
this.authenticationTag = authenticationTag;
}
}
/**
* Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
* Asymmetric encryption, with private and public key
*
* @param string String to encrypt
* @param cert contains public key in it
* @return encrypted string
*/
public static String encryptStringAsymmetric(String string, String cert)
throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException,
CertificateException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
String trimmedCert = cert.replace("-----BEGIN CERTIFICATE-----\n", "")
.replace("-----END CERTIFICATE-----\n", "");
byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(decodedCert);
X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
PublicKey realPublicKey = certificate.getPublicKey();
cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
byte[] bytes = encodeStringToBase64Bytes(string);
byte[] cryptedBytes = cipher.doFinal(bytes);
return encodeBytesToBase64String(cryptedBytes);
}
public static String encryptStringAsymmetric(String string, PublicKey publicKey) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = encodeStringToBase64Bytes(string);
byte[] cryptedBytes = cipher.doFinal(bytes);
return encodeBytesToBase64String(cryptedBytes);
}
/**
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
* Asymmetric encryption, with private and public key
*
* @param string string to decrypt
* @param privateKeyString private key
* @return decrypted string
*/
public static String decryptStringAsymmetric(String string, String privateKeyString)
throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException,
InvalidKeySpecException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey privateKey = kf.generatePrivate(keySpec);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = decodeStringToBase64Bytes(string);
byte[] encodedBytes = cipher.doFinal(bytes);
return decodeBase64BytesToString(encodedBytes);
}
public static String decryptStringAsymmetric(String string, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = decodeStringToBase64Bytes(string);
byte[] encodedBytes = cipher.doFinal(bytes);
return decodeBase64BytesToString(encodedBytes);
}
/**
* Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
* and public key
*
* @param string String to encrypt
* @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
* @return encrypted string
*/
public static String encryptStringSymmetric(String string, byte[] encryptionKeyBytes)
throws NoSuchPaddingException,
InvalidKeyException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidAlgorithmParameterException {
return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiter);
}
@VisibleForTesting
public static String encryptStringSymmetricOld(String string, byte[] encryptionKeyBytes)
throws NoSuchPaddingException,
InvalidKeyException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidAlgorithmParameterException {
return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiterOld);
}
private static String encryptStringSymmetric(String string,
byte[] encryptionKeyBytes,
String delimiter)
throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
NoSuchPaddingException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
byte[] iv = randomBytes(ivLength);
Key key = new SecretKeySpec(encryptionKeyBytes, AES);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] bytes = encodeStringToBase64Bytes(string);
byte[] cryptedBytes = cipher.doFinal(bytes);
String encodedCryptedBytes = encodeBytesToBase64String(cryptedBytes);
String encodedIV = encodeBytesToBase64String(iv);
return encodedCryptedBytes + delimiter + encodedIV;
}
/**
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
* Asymmetric encryption, with private and public key
*
* @param string string to decrypt
* @param encryptionKeyBytes key from metadata
* @return decrypted string
*/
public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
String ivString;
int delimiterPosition = string.lastIndexOf(ivDelimiter);
if (delimiterPosition == -1) {
// backward compatibility
delimiterPosition = string.lastIndexOf(ivDelimiterOld);
ivString = string.substring(delimiterPosition + ivDelimiterOld.length());
} else {
ivString = string.substring(delimiterPosition + ivDelimiter.length());
}
String cipherString = string.substring(0, delimiterPosition);
byte[] iv = new IvParameterSpec(decodeStringToBase64Bytes(ivString)).getIV();
Key key = new SecretKeySpec(encryptionKeyBytes, AES);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] bytes = decodeStringToBase64Bytes(cipherString);
byte[] encodedBytes = cipher.doFinal(bytes);
return decodeBase64BytesToString(encodedBytes);
}
/**
* Encrypt private key with symmetric AES encryption, GCM mode mode and no padding
*
* @param privateKey byte64 encoded string representation of private key
* @param keyPhrase key used for encryption, e.g. 12 random words {@link EncryptionUtils#getRandomWords(int,
* Context)}
* @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
*/
public static String encryptPrivateKey(String privateKey, String keyPhrase)
throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException,
InvalidKeySpecException {
return encryptPrivateKey(privateKey, keyPhrase, ivDelimiter);
}
@VisibleForTesting
public static String encryptPrivateKeyOld(String privateKey, String keyPhrase)
throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException,
InvalidKeySpecException {
return encryptPrivateKey(privateKey, keyPhrase, ivDelimiterOld);
}
private static String encryptPrivateKey(String privateKey, String keyPhrase, String delimiter)
throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException,
InvalidKeySpecException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] salt = randomBytes(saltLength);
KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = encodeStringToBase64Bytes(privateKey);
byte[] encrypted = cipher.doFinal(bytes);
byte[] iv = cipher.getIV();
String encodedIV = encodeBytesToBase64String(iv);
String encodedSalt = encodeBytesToBase64String(salt);
String encodedEncryptedBytes = encodeBytesToBase64String(encrypted);
return encodedEncryptedBytes + delimiter + encodedIV + delimiter + encodedSalt;
}
/**
* Decrypt private key with symmetric AES encryption, GCM mode mode and no padding
*
* @param privateKey byte64 encoded string representation of private key, IV separated with "|"
* @param keyPhrase key used for encryption, e.g. 12 random words
* {@link EncryptionUtils#getRandomWords(int, Context)}
* @return decrypted string
*/
@SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD")
public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
String[] strings;
// split up iv, salt
if (privateKey.lastIndexOf(ivDelimiter) == -1) {
// backward compatibility
strings = privateKey.split(ivDelimiterOld);
} else {
strings = privateKey.split("\\" + ivDelimiter);
}
String realPrivateKey = strings[0];
byte[] iv = decodeStringToBase64Bytes(strings[1]);
byte[] salt = decodeStringToBase64Bytes(strings[2]);
Cipher cipher = Cipher.getInstance(AES_CIPHER);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] bytes = decodeStringToBase64Bytes(realPrivateKey);
byte[] decrypted = cipher.doFinal(bytes);
String pemKey = decodeBase64BytesToString(decrypted);
return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
}
public static String privateKeyToPEM(PrivateKey privateKey) {
String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
+ "\n-----END PRIVATE KEY-----";
}
/*
Helper
*/
public static 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(TAG, e.getMessage());
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
Log_OC.e(TAG, "Error getting MD5 checksum for file", e);
}
}
}
return "";
}
public static List getRandomWords(int count, Context context) throws IOException {
InputStream ins = context.getResources().openRawResource(context.getResources()
.getIdentifier("encryption_key_words", "raw", context.getPackageName()));
InputStreamReader inputStreamReader = new InputStreamReader(ins);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
List lines = new ArrayList<>();
String line;
while ((line = bufferedReader.readLine()) != null) {
lines.add(line);
}
SecureRandom random = new SecureRandom();
List outputLines = new ArrayList<>();
for (int i = 0; i < count; i++) {
int randomLine = random.nextInt(lines.size());
outputLines.add(lines.get(randomLine));
}
return outputLines;
}
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
keyGen.initialize(2048, new SecureRandom());
return keyGen.generateKeyPair();
}
public static byte[] generateKey() {
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(AES);
keyGenerator.init(128);
return keyGenerator.generateKey().getEncoded();
} catch (NoSuchAlgorithmException e) {
Log_OC.e(TAG, e.getMessage());
}
return null;
}
public static byte[] randomBytes(int size) {
SecureRandom random = new SecureRandom();
final byte[] iv = new byte[size];
random.nextBytes(iv);
return iv;
}
/**
* Generate a SHA512 with appended salt
*
* @param token token to be hashed
* @return SHA512 with appended salt, delimiter HASH_DELIMITER
*/
public static String generateSHA512(String token) {
String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
return generateSHA512(token, salt);
}
/**
* Generate a SHA512 with appended salt
*
* @param token token to be hashed
* @return SHA512 with appended salt, delimiter HASH_DELIMITER
*/
public static String generateSHA512(String token, String salt) {
MessageDigest digest;
String hashedToken = "";
byte[] hash;
try {
digest = MessageDigest.getInstance("SHA-512");
digest.update(salt.getBytes());
hash = digest.digest(token.getBytes());
StringBuilder stringBuilder = new StringBuilder();
for (byte hashByte : hash) {
stringBuilder.append(Integer.toString((hashByte & 0xff) + 0x100, 16).substring(1));
}
stringBuilder.append(HASH_DELIMITER).append(salt);
hashedToken = stringBuilder.toString();
} catch (NoSuchAlgorithmException e) {
Log_OC.e(TAG, "Generating SHA512 failed", e);
}
return hashedToken;
}
public static boolean verifySHA512(String hashWithSalt, String compareToken) {
String salt = hashWithSalt.split("\\" + HASH_DELIMITER)[1];
String newHash = generateSHA512(compareToken, salt);
return hashWithSalt.equals(newHash);
}
public static String lockFolder(OCFile parentFile, OwnCloudClient client) throws UploadException {
// Lock folder
LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId());
RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
if (lockFileOperationResult.isSuccess() &&
!TextUtils.isEmpty((String) lockFileOperationResult.getData().get(0))) {
return (String) lockFileOperationResult.getData().get(0);
} else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
throw new UploadException("Forbidden! Please try again later.)");
} else {
throw new UploadException("Could not lock folder");
}
}
/**
* @param parentFile file metadata should be retrieved for
* @return Pair: boolean: true: metadata already exists, false: metadata new created
*/
public static Pair retrieveMetadata(OCFile parentFile,
OwnCloudClient client,
String privateKey,
String publicKey) throws UploadException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
DecryptedFolderMetadata metadata;
if (getMetadataOperationResult.isSuccess()) {
// decrypt metadata
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
serializedEncryptedMetadata, new TypeToken() {
});
return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey));
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
// new metadata
metadata = new DecryptedFolderMetadata();
metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
metadata.getMetadata().setMetadataKeys(new HashMap<>());
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
return new Pair<>(Boolean.FALSE, metadata);
} else {
// TODO error
throw new UploadException("something wrong");
}
}
public static void uploadMetadata(OCFile parentFile,
String serializedFolderMetadata,
String token,
OwnCloudClient client,
boolean metadataExists) throws UploadException {
RemoteOperationResult uploadMetadataOperationResult;
if (metadataExists) {
// update metadata
UpdateMetadataRemoteOperation storeMetadataOperation = new UpdateMetadataRemoteOperation(
parentFile.getLocalId(), serializedFolderMetadata, token);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
} else {
// store metadata
StoreMetadataRemoteOperation storeMetadataOperation = new StoreMetadataRemoteOperation(
parentFile.getLocalId(), serializedFolderMetadata);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
}
if (!uploadMetadataOperationResult.isSuccess()) {
throw new UploadException("Storing/updating metadata was not successful");
}
}
public static RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
if (token != null) {
return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
} else {
return new RemoteOperationResult(new Exception("No token available"));
}
}
}