EncryptionUtils.java 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Tobias Kaminsky
  5. * Copyright (C) 2017 Tobias Kaminsky
  6. * Copyright (C) 2017 Nextcloud GmbH.
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. package com.owncloud.android.utils;
  22. import android.accounts.Account;
  23. import android.content.Context;
  24. import android.text.TextUtils;
  25. import android.util.Base64;
  26. import android.util.Pair;
  27. import com.google.gson.Gson;
  28. import com.google.gson.reflect.TypeToken;
  29. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  30. import com.owncloud.android.datamodel.DecryptedFolderMetadata;
  31. import com.owncloud.android.datamodel.EncryptedFolderMetadata;
  32. import com.owncloud.android.datamodel.OCFile;
  33. import com.owncloud.android.lib.common.OwnCloudClient;
  34. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  35. import com.owncloud.android.lib.common.utils.Log_OC;
  36. import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
  37. import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
  38. import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
  39. import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
  40. import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
  41. import com.owncloud.android.operations.UploadException;
  42. import org.apache.commons.codec.binary.Hex;
  43. import org.apache.commons.httpclient.HttpStatus;
  44. import java.io.BufferedReader;
  45. import java.io.ByteArrayInputStream;
  46. import java.io.File;
  47. import java.io.FileInputStream;
  48. import java.io.IOException;
  49. import java.io.InputStream;
  50. import java.io.InputStreamReader;
  51. import java.io.RandomAccessFile;
  52. import java.nio.charset.StandardCharsets;
  53. import java.security.InvalidAlgorithmParameterException;
  54. import java.security.InvalidKeyException;
  55. import java.security.Key;
  56. import java.security.KeyFactory;
  57. import java.security.KeyPair;
  58. import java.security.KeyPairGenerator;
  59. import java.security.MessageDigest;
  60. import java.security.NoSuchAlgorithmException;
  61. import java.security.PrivateKey;
  62. import java.security.PublicKey;
  63. import java.security.SecureRandom;
  64. import java.security.cert.CertificateException;
  65. import java.security.cert.CertificateFactory;
  66. import java.security.cert.X509Certificate;
  67. import java.security.spec.InvalidKeySpecException;
  68. import java.security.spec.KeySpec;
  69. import java.security.spec.PKCS8EncodedKeySpec;
  70. import java.util.ArrayList;
  71. import java.util.Arrays;
  72. import java.util.HashMap;
  73. import java.util.List;
  74. import java.util.Map;
  75. import javax.crypto.BadPaddingException;
  76. import javax.crypto.Cipher;
  77. import javax.crypto.IllegalBlockSizeException;
  78. import javax.crypto.KeyGenerator;
  79. import javax.crypto.NoSuchPaddingException;
  80. import javax.crypto.SecretKey;
  81. import javax.crypto.SecretKeyFactory;
  82. import javax.crypto.spec.GCMParameterSpec;
  83. import javax.crypto.spec.IvParameterSpec;
  84. import javax.crypto.spec.PBEKeySpec;
  85. import javax.crypto.spec.SecretKeySpec;
  86. import androidx.annotation.Nullable;
  87. import androidx.annotation.VisibleForTesting;
  88. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  89. /**
  90. * Utils for encryption
  91. */
  92. public final class EncryptionUtils {
  93. private static String TAG = EncryptionUtils.class.getSimpleName();
  94. public static final String PUBLIC_KEY = "PUBLIC_KEY";
  95. public static final String PRIVATE_KEY = "PRIVATE_KEY";
  96. public static final String MNEMONIC = "MNEMONIC";
  97. public static final int ivLength = 16;
  98. public static final int saltLength = 40;
  99. public static final String ivDelimiter = "|"; // not base64 encoded
  100. public static final String ivDelimiterOld = "fA=="; // "|" base64 encoded
  101. private static final String HASH_DELIMITER = "$";
  102. private static final int iterationCount = 1024;
  103. private static final int keyStrength = 256;
  104. private static final String AES_CIPHER = "AES/GCM/NoPadding";
  105. private static final String AES = "AES";
  106. private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
  107. private static final String RSA = "RSA";
  108. private EncryptionUtils() {
  109. // utility class -> private constructor
  110. }
  111. /*
  112. JSON
  113. */
  114. public static <T> T deserializeJSON(String json, TypeToken<T> type) {
  115. return new Gson().fromJson(json, type.getType());
  116. }
  117. public static String serializeJSON(Object data) {
  118. return new Gson().toJson(data);
  119. }
  120. /*
  121. METADATA
  122. */
  123. /**
  124. * Encrypt folder metaData
  125. *
  126. * @param decryptedFolderMetadata folder metaData to encrypt
  127. * @return EncryptedFolderMetadata encrypted folder metadata
  128. */
  129. public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
  130. String privateKey)
  131. throws NoSuchAlgorithmException, InvalidKeyException,
  132. InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
  133. IllegalBlockSizeException, InvalidKeySpecException {
  134. HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
  135. EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
  136. .getMetadata(), files);
  137. // Encrypt each file in "files"
  138. for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
  139. .getFiles().entrySet()) {
  140. String key = entry.getKey();
  141. DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
  142. EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
  143. encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
  144. encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
  145. encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
  146. byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric(
  147. decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()),
  148. privateKey));
  149. // encrypt
  150. String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
  151. encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, decryptedMetadataKey));
  152. files.put(key, encryptedFile);
  153. }
  154. return encryptedFolderMetadata;
  155. }
  156. /*
  157. * decrypt folder metaData with private key
  158. */
  159. public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
  160. String privateKey)
  161. throws NoSuchAlgorithmException, InvalidKeyException,
  162. InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
  163. IllegalBlockSizeException, InvalidKeySpecException {
  164. HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
  165. DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
  166. encryptedFolderMetadata.getMetadata(), files);
  167. for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
  168. .getFiles().entrySet()) {
  169. String key = entry.getKey();
  170. EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
  171. DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
  172. decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
  173. decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
  174. decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
  175. byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
  176. EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
  177. .getMetadataKeys().get(encryptedFile.getMetadataKey()), privateKey));
  178. // decrypt
  179. String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
  180. decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
  181. new TypeToken<DecryptedFolderMetadata.Data>() {
  182. }));
  183. files.put(key, decryptedFile);
  184. }
  185. return decryptedFolderMetadata;
  186. }
  187. /**
  188. * Download metadata for folder and decrypt it
  189. *
  190. * @return decrypted metadata or null
  191. */
  192. public static @Nullable
  193. DecryptedFolderMetadata downloadFolderMetadata(OCFile folder, OwnCloudClient client,
  194. Context context, Account account) {
  195. RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId())
  196. .execute(client);
  197. if (!getMetadataOperationResult.isSuccess()) {
  198. return null;
  199. }
  200. // decrypt metadata
  201. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
  202. String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
  203. String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
  204. EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  205. serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
  206. });
  207. try {
  208. return EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
  209. } catch (Exception e) {
  210. Log_OC.e(TAG, e.getMessage());
  211. return null;
  212. }
  213. }
  214. /*
  215. BASE 64
  216. */
  217. public static byte[] encodeStringToBase64Bytes(String string) {
  218. try {
  219. return Base64.encode(string.getBytes(), Base64.NO_WRAP);
  220. } catch (Exception e) {
  221. return new byte[0];
  222. }
  223. }
  224. public static String decodeBase64BytesToString(byte[] bytes) {
  225. try {
  226. return new String(Base64.decode(bytes, Base64.NO_WRAP));
  227. } catch (Exception e) {
  228. return "";
  229. }
  230. }
  231. public static String encodeBytesToBase64String(byte[] bytes) {
  232. return Base64.encodeToString(bytes, Base64.NO_WRAP);
  233. }
  234. public static byte[] decodeStringToBase64Bytes(String string) {
  235. return Base64.decode(string, Base64.NO_WRAP);
  236. }
  237. /*
  238. ENCRYPTION
  239. */
  240. /**
  241. * @param ocFile file do crypt
  242. * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
  243. * @param iv initialization vector, either from metadata or {@link EncryptionUtils#randomBytes(int)}
  244. * @return encryptedFile with encryptedBytes and authenticationTag
  245. */
  246. public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
  247. throws NoSuchAlgorithmException,
  248. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  249. BadPaddingException, IllegalBlockSizeException, IOException {
  250. File file = new File(ocFile.getStoragePath());
  251. return encryptFile(file, encryptionKeyBytes, iv);
  252. }
  253. /**
  254. * @param file file do crypt
  255. * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
  256. * @param iv initialization vector, either from metadata or {@link EncryptionUtils#randomBytes(int)}
  257. * @return encryptedFile with encryptedBytes and authenticationTag
  258. */
  259. public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
  260. throws NoSuchAlgorithmException,
  261. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  262. BadPaddingException, IllegalBlockSizeException, IOException {
  263. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  264. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  265. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  266. cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  267. RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
  268. byte[] fileBytes = new byte[(int) randomAccessFile.length()];
  269. randomAccessFile.readFully(fileBytes);
  270. byte[] cryptedBytes = cipher.doFinal(fileBytes);
  271. String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
  272. cryptedBytes.length - (128 / 8), cryptedBytes.length));
  273. return new EncryptedFile(cryptedBytes, authenticationTag);
  274. }
  275. /**
  276. * @param file encrypted file
  277. * @param encryptionKeyBytes key from metadata
  278. * @param iv initialization vector from metadata
  279. * @param authenticationTag authenticationTag from metadata
  280. * @return decrypted byte[]
  281. */
  282. public static byte[] decryptFile(File file, byte[] encryptionKeyBytes, byte[] iv, byte[] authenticationTag)
  283. throws NoSuchAlgorithmException,
  284. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  285. BadPaddingException, IllegalBlockSizeException, IOException {
  286. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  287. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  288. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  289. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  290. RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
  291. byte[] fileBytes = new byte[(int) randomAccessFile.length()];
  292. randomAccessFile.readFully(fileBytes);
  293. // check authentication tag
  294. byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
  295. fileBytes.length - (128 / 8), fileBytes.length);
  296. if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
  297. throw new SecurityException("Tag not correct");
  298. }
  299. return cipher.doFinal(fileBytes);
  300. }
  301. public static class EncryptedFile {
  302. public byte[] encryptedBytes;
  303. public String authenticationTag;
  304. public EncryptedFile(byte[] encryptedBytes, String authenticationTag) {
  305. this.encryptedBytes = encryptedBytes;
  306. this.authenticationTag = authenticationTag;
  307. }
  308. }
  309. /**
  310. * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
  311. * Asymmetric encryption, with private and public key
  312. *
  313. * @param string String to encrypt
  314. * @param cert contains public key in it
  315. * @return encrypted string
  316. */
  317. public static String encryptStringAsymmetric(String string, String cert)
  318. throws NoSuchAlgorithmException,
  319. NoSuchPaddingException, InvalidKeyException,
  320. BadPaddingException, IllegalBlockSizeException,
  321. CertificateException {
  322. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  323. String trimmedCert = cert.replace("-----BEGIN CERTIFICATE-----\n", "")
  324. .replace("-----END CERTIFICATE-----\n", "");
  325. byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
  326. byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
  327. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  328. InputStream in = new ByteArrayInputStream(decodedCert);
  329. X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
  330. PublicKey realPublicKey = certificate.getPublicKey();
  331. cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
  332. byte[] bytes = encodeStringToBase64Bytes(string);
  333. byte[] cryptedBytes = cipher.doFinal(bytes);
  334. return encodeBytesToBase64String(cryptedBytes);
  335. }
  336. public static String encryptStringAsymmetric(String string, PublicKey publicKey) throws NoSuchPaddingException,
  337. NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  338. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  339. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  340. byte[] bytes = encodeStringToBase64Bytes(string);
  341. byte[] cryptedBytes = cipher.doFinal(bytes);
  342. return encodeBytesToBase64String(cryptedBytes);
  343. }
  344. /**
  345. * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
  346. * Asymmetric encryption, with private and public key
  347. *
  348. * @param string string to decrypt
  349. * @param privateKeyString private key
  350. * @return decrypted string
  351. */
  352. public static String decryptStringAsymmetric(String string, String privateKeyString)
  353. throws NoSuchAlgorithmException,
  354. NoSuchPaddingException, InvalidKeyException,
  355. BadPaddingException, IllegalBlockSizeException,
  356. InvalidKeySpecException {
  357. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  358. byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
  359. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  360. KeyFactory kf = KeyFactory.getInstance(RSA);
  361. PrivateKey privateKey = kf.generatePrivate(keySpec);
  362. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  363. byte[] bytes = decodeStringToBase64Bytes(string);
  364. byte[] encodedBytes = cipher.doFinal(bytes);
  365. return decodeBase64BytesToString(encodedBytes);
  366. }
  367. public static String decryptStringAsymmetric(String string, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  368. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  369. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  370. byte[] bytes = decodeStringToBase64Bytes(string);
  371. byte[] encodedBytes = cipher.doFinal(bytes);
  372. return decodeBase64BytesToString(encodedBytes);
  373. }
  374. /**
  375. * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  376. * and public key
  377. *
  378. * @param string String to encrypt
  379. * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
  380. * @return encrypted string
  381. */
  382. public static String encryptStringSymmetric(String string, byte[] encryptionKeyBytes)
  383. throws NoSuchPaddingException,
  384. InvalidKeyException,
  385. NoSuchAlgorithmException,
  386. IllegalBlockSizeException,
  387. BadPaddingException,
  388. InvalidAlgorithmParameterException {
  389. return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiter);
  390. }
  391. @VisibleForTesting
  392. public static String encryptStringSymmetricOld(String string, byte[] encryptionKeyBytes)
  393. throws NoSuchPaddingException,
  394. InvalidKeyException,
  395. NoSuchAlgorithmException,
  396. IllegalBlockSizeException,
  397. BadPaddingException,
  398. InvalidAlgorithmParameterException {
  399. return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiterOld);
  400. }
  401. private static String encryptStringSymmetric(String string,
  402. byte[] encryptionKeyBytes,
  403. String delimiter)
  404. throws NoSuchAlgorithmException,
  405. InvalidAlgorithmParameterException,
  406. NoSuchPaddingException,
  407. InvalidKeyException,
  408. BadPaddingException,
  409. IllegalBlockSizeException {
  410. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  411. byte[] iv = randomBytes(ivLength);
  412. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  413. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  414. cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  415. byte[] bytes = encodeStringToBase64Bytes(string);
  416. byte[] cryptedBytes = cipher.doFinal(bytes);
  417. String encodedCryptedBytes = encodeBytesToBase64String(cryptedBytes);
  418. String encodedIV = encodeBytesToBase64String(iv);
  419. return encodedCryptedBytes + delimiter + encodedIV;
  420. }
  421. /**
  422. * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
  423. * Asymmetric encryption, with private and public key
  424. *
  425. * @param string string to decrypt
  426. * @param encryptionKeyBytes key from metadata
  427. * @return decrypted string
  428. */
  429. public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
  430. throws NoSuchAlgorithmException,
  431. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  432. BadPaddingException, IllegalBlockSizeException {
  433. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  434. String ivString;
  435. int delimiterPosition = string.lastIndexOf(ivDelimiter);
  436. if (delimiterPosition == -1) {
  437. // backward compatibility
  438. delimiterPosition = string.lastIndexOf(ivDelimiterOld);
  439. ivString = string.substring(delimiterPosition + ivDelimiterOld.length());
  440. } else {
  441. ivString = string.substring(delimiterPosition + ivDelimiter.length());
  442. }
  443. String cipherString = string.substring(0, delimiterPosition);
  444. byte[] iv = new IvParameterSpec(decodeStringToBase64Bytes(ivString)).getIV();
  445. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  446. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  447. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  448. byte[] bytes = decodeStringToBase64Bytes(cipherString);
  449. byte[] encodedBytes = cipher.doFinal(bytes);
  450. return decodeBase64BytesToString(encodedBytes);
  451. }
  452. /**
  453. * Encrypt private key with symmetric AES encryption, GCM mode mode and no padding
  454. *
  455. * @param privateKey byte64 encoded string representation of private key
  456. * @param keyPhrase key used for encryption, e.g. 12 random words {@link EncryptionUtils#getRandomWords(int,
  457. * Context)}
  458. * @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
  459. */
  460. public static String encryptPrivateKey(String privateKey, String keyPhrase)
  461. throws NoSuchPaddingException,
  462. NoSuchAlgorithmException,
  463. InvalidKeyException,
  464. BadPaddingException,
  465. IllegalBlockSizeException,
  466. InvalidKeySpecException {
  467. return encryptPrivateKey(privateKey, keyPhrase, ivDelimiter);
  468. }
  469. @VisibleForTesting
  470. public static String encryptPrivateKeyOld(String privateKey, String keyPhrase)
  471. throws NoSuchPaddingException,
  472. NoSuchAlgorithmException,
  473. InvalidKeyException,
  474. BadPaddingException,
  475. IllegalBlockSizeException,
  476. InvalidKeySpecException {
  477. return encryptPrivateKey(privateKey, keyPhrase, ivDelimiterOld);
  478. }
  479. private static String encryptPrivateKey(String privateKey, String keyPhrase, String delimiter)
  480. throws NoSuchPaddingException,
  481. NoSuchAlgorithmException,
  482. InvalidKeyException,
  483. BadPaddingException,
  484. IllegalBlockSizeException,
  485. InvalidKeySpecException {
  486. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  487. SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
  488. byte[] salt = randomBytes(saltLength);
  489. KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
  490. SecretKey tmp = factory.generateSecret(spec);
  491. SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
  492. cipher.init(Cipher.ENCRYPT_MODE, key);
  493. byte[] bytes = encodeStringToBase64Bytes(privateKey);
  494. byte[] encrypted = cipher.doFinal(bytes);
  495. byte[] iv = cipher.getIV();
  496. String encodedIV = encodeBytesToBase64String(iv);
  497. String encodedSalt = encodeBytesToBase64String(salt);
  498. String encodedEncryptedBytes = encodeBytesToBase64String(encrypted);
  499. return encodedEncryptedBytes + delimiter + encodedIV + delimiter + encodedSalt;
  500. }
  501. /**
  502. * Decrypt private key with symmetric AES encryption, GCM mode mode and no padding
  503. *
  504. * @param privateKey byte64 encoded string representation of private key, IV separated with "|"
  505. * @param keyPhrase key used for encryption, e.g. 12 random words
  506. * {@link EncryptionUtils#getRandomWords(int, Context)}
  507. * @return decrypted string
  508. */
  509. @SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD")
  510. public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
  511. NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
  512. IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
  513. String[] strings;
  514. // split up iv, salt
  515. if (privateKey.lastIndexOf(ivDelimiter) == -1) {
  516. // backward compatibility
  517. strings = privateKey.split(ivDelimiterOld);
  518. } else {
  519. strings = privateKey.split("\\" + ivDelimiter);
  520. }
  521. String realPrivateKey = strings[0];
  522. byte[] iv = decodeStringToBase64Bytes(strings[1]);
  523. byte[] salt = decodeStringToBase64Bytes(strings[2]);
  524. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  525. SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
  526. KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
  527. SecretKey tmp = factory.generateSecret(spec);
  528. SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
  529. cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
  530. byte[] bytes = decodeStringToBase64Bytes(realPrivateKey);
  531. byte[] decrypted = cipher.doFinal(bytes);
  532. String pemKey = decodeBase64BytesToString(decrypted);
  533. return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "")
  534. .replace("-----END PRIVATE KEY-----", "");
  535. }
  536. public static String privateKeyToPEM(PrivateKey privateKey) {
  537. String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
  538. return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
  539. + "\n-----END PRIVATE KEY-----";
  540. }
  541. /*
  542. Helper
  543. */
  544. public static String getMD5Sum(File file) {
  545. FileInputStream fileInputStream = null;
  546. try {
  547. fileInputStream = new FileInputStream(file);
  548. MessageDigest md5 = MessageDigest.getInstance("MD5");
  549. byte[] bytes = new byte[2048];
  550. int readBytes;
  551. while ((readBytes = fileInputStream.read(bytes)) != -1) {
  552. md5.update(bytes, 0, readBytes);
  553. }
  554. return new String(Hex.encodeHex(md5.digest()));
  555. } catch (Exception e) {
  556. Log_OC.e(TAG, e.getMessage());
  557. } finally {
  558. if (fileInputStream != null) {
  559. try {
  560. fileInputStream.close();
  561. } catch (IOException e) {
  562. Log_OC.e(TAG, "Error getting MD5 checksum for file", e);
  563. }
  564. }
  565. }
  566. return "";
  567. }
  568. public static List<String> getRandomWords(int count, Context context) throws IOException {
  569. InputStream ins = context.getResources().openRawResource(context.getResources()
  570. .getIdentifier("encryption_key_words", "raw", context.getPackageName()));
  571. InputStreamReader inputStreamReader = new InputStreamReader(ins);
  572. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  573. List<String> lines = new ArrayList<>();
  574. String line;
  575. while ((line = bufferedReader.readLine()) != null) {
  576. lines.add(line);
  577. }
  578. SecureRandom random = new SecureRandom();
  579. List<String> outputLines = new ArrayList<>();
  580. for (int i = 0; i < count; i++) {
  581. int randomLine = random.nextInt(lines.size());
  582. outputLines.add(lines.get(randomLine));
  583. }
  584. return outputLines;
  585. }
  586. public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
  587. KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
  588. keyGen.initialize(2048, new SecureRandom());
  589. return keyGen.generateKeyPair();
  590. }
  591. public static byte[] generateKey() {
  592. KeyGenerator keyGenerator;
  593. try {
  594. keyGenerator = KeyGenerator.getInstance(AES);
  595. keyGenerator.init(128);
  596. return keyGenerator.generateKey().getEncoded();
  597. } catch (NoSuchAlgorithmException e) {
  598. Log_OC.e(TAG, e.getMessage());
  599. }
  600. return null;
  601. }
  602. public static byte[] randomBytes(int size) {
  603. SecureRandom random = new SecureRandom();
  604. final byte[] iv = new byte[size];
  605. random.nextBytes(iv);
  606. return iv;
  607. }
  608. /**
  609. * Generate a SHA512 with appended salt
  610. *
  611. * @param token token to be hashed
  612. * @return SHA512 with appended salt, delimiter HASH_DELIMITER
  613. */
  614. public static String generateSHA512(String token) {
  615. String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
  616. return generateSHA512(token, salt);
  617. }
  618. /**
  619. * Generate a SHA512 with appended salt
  620. *
  621. * @param token token to be hashed
  622. * @return SHA512 with appended salt, delimiter HASH_DELIMITER
  623. */
  624. public static String generateSHA512(String token, String salt) {
  625. MessageDigest digest;
  626. String hashedToken = "";
  627. byte[] hash;
  628. try {
  629. digest = MessageDigest.getInstance("SHA-512");
  630. digest.update(salt.getBytes());
  631. hash = digest.digest(token.getBytes());
  632. StringBuilder stringBuilder = new StringBuilder();
  633. for (byte hashByte : hash) {
  634. stringBuilder.append(Integer.toString((hashByte & 0xff) + 0x100, 16).substring(1));
  635. }
  636. stringBuilder.append(HASH_DELIMITER).append(salt);
  637. hashedToken = stringBuilder.toString();
  638. } catch (NoSuchAlgorithmException e) {
  639. Log_OC.e(TAG, "Generating SHA512 failed", e);
  640. }
  641. return hashedToken;
  642. }
  643. public static boolean verifySHA512(String hashWithSalt, String compareToken) {
  644. String salt = hashWithSalt.split("\\" + HASH_DELIMITER)[1];
  645. String newHash = generateSHA512(compareToken, salt);
  646. return hashWithSalt.equals(newHash);
  647. }
  648. public static String lockFolder(OCFile parentFile, OwnCloudClient client) throws UploadException {
  649. // Lock folder
  650. LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId());
  651. RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
  652. if (lockFileOperationResult.isSuccess() &&
  653. !TextUtils.isEmpty((String) lockFileOperationResult.getData().get(0))) {
  654. return (String) lockFileOperationResult.getData().get(0);
  655. } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
  656. throw new UploadException("Forbidden! Please try again later.)");
  657. } else {
  658. throw new UploadException("Could not lock folder");
  659. }
  660. }
  661. /**
  662. * @param parentFile file metadata should be retrieved for
  663. * @return Pair: boolean: true: metadata already exists, false: metadata new created
  664. */
  665. public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
  666. OwnCloudClient client,
  667. String privateKey,
  668. String publicKey) throws UploadException,
  669. InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
  670. IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
  671. GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
  672. RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
  673. DecryptedFolderMetadata metadata;
  674. if (getMetadataOperationResult.isSuccess()) {
  675. // decrypt metadata
  676. String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
  677. EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  678. serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
  679. });
  680. return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey));
  681. } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
  682. // new metadata
  683. metadata = new DecryptedFolderMetadata();
  684. metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
  685. metadata.getMetadata().setMetadataKeys(new HashMap<>());
  686. String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
  687. String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
  688. metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
  689. return new Pair<>(Boolean.FALSE, metadata);
  690. } else {
  691. // TODO error
  692. throw new UploadException("something wrong");
  693. }
  694. }
  695. public static void uploadMetadata(OCFile parentFile,
  696. String serializedFolderMetadata,
  697. String token,
  698. OwnCloudClient client,
  699. boolean metadataExists) throws UploadException {
  700. RemoteOperationResult uploadMetadataOperationResult;
  701. if (metadataExists) {
  702. // update metadata
  703. UpdateMetadataRemoteOperation storeMetadataOperation = new UpdateMetadataRemoteOperation(
  704. parentFile.getLocalId(), serializedFolderMetadata, token);
  705. uploadMetadataOperationResult = storeMetadataOperation.execute(client);
  706. } else {
  707. // store metadata
  708. StoreMetadataRemoteOperation storeMetadataOperation = new StoreMetadataRemoteOperation(
  709. parentFile.getLocalId(), serializedFolderMetadata);
  710. uploadMetadataOperationResult = storeMetadataOperation.execute(client);
  711. }
  712. if (!uploadMetadataOperationResult.isSuccess()) {
  713. throw new UploadException("Storing/updating metadata was not successful");
  714. }
  715. }
  716. public static RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
  717. if (token != null) {
  718. return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
  719. } else {
  720. return new RemoteOperationResult(new Exception("No token available"));
  721. }
  722. }
  723. }