/* * 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")); } } }