EncryptionUtils.java 34 KB

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