EncryptionUtils.java 28 KB

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