EncryptionUtils.java 36 KB

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