EncryptionUtils.java 27 KB

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