That fuck shit the fascists are using
1/**
2 * Copyright (C) 2011 Whisper Systems
3 * Copyright (C) 2013 Open Whisper Systems
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18package org.tm.archive.crypto;
19
20import android.content.Context;
21import android.content.SharedPreferences;
22import android.text.TextUtils;
23
24import androidx.annotation.NonNull;
25import androidx.annotation.Nullable;
26
27import org.signal.core.util.logging.Log;
28import org.signal.libsignal.protocol.InvalidKeyException;
29import org.signal.libsignal.protocol.ecc.Curve;
30import org.signal.libsignal.protocol.ecc.ECKeyPair;
31import org.signal.libsignal.protocol.ecc.ECPrivateKey;
32import org.signal.libsignal.protocol.ecc.ECPublicKey;
33import org.signal.core.util.Base64;
34import org.tm.archive.util.Util;
35
36import java.io.IOException;
37import java.security.GeneralSecurityException;
38import java.security.NoSuchAlgorithmException;
39import java.security.SecureRandom;
40import java.security.spec.InvalidKeySpecException;
41import java.util.Arrays;
42
43import javax.crypto.Cipher;
44import javax.crypto.KeyGenerator;
45import javax.crypto.Mac;
46import javax.crypto.SecretKey;
47import javax.crypto.SecretKeyFactory;
48import javax.crypto.spec.PBEKeySpec;
49import javax.crypto.spec.PBEParameterSpec;
50import javax.crypto.spec.SecretKeySpec;
51
52/**
53 * Helper class for generating and securely storing a MasterSecret.
54 *
55 * @author Moxie Marlinspike
56 */
57
58public class MasterSecretUtil {
59
60 private static final String TAG = Log.tag(MasterSecretUtil.class);
61
62 public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
63 public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
64
65 private static final String ASYMMETRIC_LOCAL_PUBLIC_DJB = "asymmetric_master_secret_curve25519_public";
66 private static final String ASYMMETRIC_LOCAL_PRIVATE_DJB = "asymmetric_master_secret_curve25519_private";
67
68 private static SharedPreferences preferences;
69
70 public static MasterSecret changeMasterSecretPassphrase(Context context,
71 MasterSecret masterSecret,
72 String newPassphrase)
73 {
74 try {
75 byte[] combinedSecrets = Util.combine(masterSecret.getEncryptionKey().getEncoded(),
76 masterSecret.getMacKey().getEncoded());
77
78 byte[] encryptionSalt = generateSalt();
79 int iterations = generateIterationCount(newPassphrase, encryptionSalt);
80 byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, combinedSecrets, newPassphrase);
81 byte[] macSalt = generateSalt();
82 byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, newPassphrase);
83
84 save(context, "encryption_salt", encryptionSalt);
85 save(context, "mac_salt", macSalt);
86 save(context, "passphrase_iterations", iterations);
87 save(context, "master_secret", encryptedAndMacdMasterSecret);
88 save(context, "passphrase_initialized", true);
89
90 return masterSecret;
91 } catch (GeneralSecurityException gse) {
92 throw new AssertionError(gse);
93 }
94 }
95
96 public static MasterSecret changeMasterSecretPassphrase(Context context,
97 String originalPassphrase,
98 String newPassphrase)
99 throws InvalidPassphraseException
100 {
101 MasterSecret masterSecret = getMasterSecret(context, originalPassphrase);
102 changeMasterSecretPassphrase(context, masterSecret, newPassphrase);
103
104 return masterSecret;
105 }
106
107 public static MasterSecret getMasterSecret(Context context, String passphrase)
108 throws InvalidPassphraseException
109 {
110 try {
111 byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
112 byte[] macSalt = retrieve(context, "mac_salt");
113 int iterations = retrieve(context, "passphrase_iterations", 100);
114 byte[] encryptedMasterSecret = verifyMac(macSalt, iterations, encryptedAndMacdMasterSecret, passphrase);
115 byte[] encryptionSalt = retrieve(context, "encryption_salt");
116 byte[] combinedSecrets = decryptWithPassphrase(encryptionSalt, iterations, encryptedMasterSecret, passphrase);
117 byte[] encryptionSecret = Util.split(combinedSecrets, 16, 20)[0];
118 byte[] macSecret = Util.split(combinedSecrets, 16, 20)[1];
119
120 return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
121 new SecretKeySpec(macSecret, "HmacSHA1"));
122 } catch (GeneralSecurityException e) {
123 Log.w(TAG, e);
124 return null; //XXX
125 } catch (IOException e) {
126 Log.w(TAG, e);
127 return null; //XXX
128 }
129 }
130
131 public static AsymmetricMasterSecret getAsymmetricMasterSecret(@NonNull Context context,
132 @Nullable MasterSecret masterSecret)
133 {
134 try {
135 byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB);
136 byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB);
137
138 ECPublicKey djbPublicKey = null;
139 ECPrivateKey djbPrivateKey = null;
140
141 if (djbPublicBytes != null) {
142 djbPublicKey = Curve.decodePoint(djbPublicBytes, 0);
143 }
144
145 if (masterSecret != null) {
146 MasterCipher masterCipher = new MasterCipher(masterSecret);
147
148 if (djbPrivateBytes != null) {
149 djbPrivateKey = masterCipher.decryptKey(djbPrivateBytes);
150 }
151 }
152
153 return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey);
154 } catch (InvalidKeyException | IOException ike) {
155 throw new AssertionError(ike);
156 }
157 }
158
159 public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context,
160 MasterSecret masterSecret)
161 {
162 MasterCipher masterCipher = new MasterCipher(masterSecret);
163 ECKeyPair keyPair = Curve.generateKeyPair();
164
165 save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
166 save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey()));
167
168 return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey());
169 }
170
171 public static MasterSecret generateMasterSecret(Context context, String passphrase) {
172 try {
173 byte[] encryptionSecret = generateEncryptionSecret();
174 byte[] macSecret = generateMacSecret();
175 byte[] masterSecret = Util.combine(encryptionSecret, macSecret);
176 byte[] encryptionSalt = generateSalt();
177 int iterations = generateIterationCount(passphrase, encryptionSalt);
178 byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, masterSecret, passphrase);
179 byte[] macSalt = generateSalt();
180 byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, passphrase);
181
182 save(context, "encryption_salt", encryptionSalt);
183 save(context, "mac_salt", macSalt);
184 save(context, "passphrase_iterations", iterations);
185 save(context, "master_secret", encryptedAndMacdMasterSecret);
186 save(context, "passphrase_initialized", true);
187
188 return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
189 new SecretKeySpec(macSecret, "HmacSHA1"));
190 } catch (GeneralSecurityException e) {
191 Log.w(TAG, e);
192 return null;
193 }
194 }
195
196 public static boolean hasAsymmericMasterSecret(Context context) {
197 SharedPreferences settings = getSharedPreferences(context);
198 return settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB);
199 }
200
201 public static boolean isPassphraseInitialized(Context context) {
202 SharedPreferences preferences = getSharedPreferences(context);
203 return preferences.getBoolean("passphrase_initialized", false);
204 }
205
206 private static void save(Context context, String key, int value) {
207 if (!getSharedPreferences(context)
208 .edit()
209 .putInt(key, value)
210 .commit())
211 {
212 throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
213 }
214 }
215
216 private static void save(Context context, String key, byte[] value) {
217 if (!getSharedPreferences(context)
218 .edit()
219 .putString(key, Base64.encodeWithPadding(value))
220 .commit())
221 {
222 throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
223 }
224 }
225
226 private static void save(Context context, String key, boolean value) {
227 if (!getSharedPreferences(context)
228 .edit()
229 .putBoolean(key, value)
230 .commit())
231 {
232 throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
233 }
234 }
235
236 private static byte[] retrieve(Context context, String key) throws IOException {
237 SharedPreferences settings = getSharedPreferences(context);
238 String encodedValue = settings.getString(key, "");
239
240 if (TextUtils.isEmpty(encodedValue)) return null;
241 else return Base64.decode(encodedValue);
242 }
243
244 private static int retrieve(Context context, String key, int defaultValue) throws IOException {
245 SharedPreferences settings = getSharedPreferences(context);
246 return settings.getInt(key, defaultValue);
247 }
248
249 private static byte[] generateEncryptionSecret() {
250 try {
251 KeyGenerator generator = KeyGenerator.getInstance("AES");
252 generator.init(128);
253
254 SecretKey key = generator.generateKey();
255 return key.getEncoded();
256 } catch (NoSuchAlgorithmException ex) {
257 Log.w(TAG, ex);
258 return null;
259 }
260 }
261
262 private static byte[] generateMacSecret() {
263 try {
264 KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
265 return generator.generateKey().getEncoded();
266 } catch (NoSuchAlgorithmException e) {
267 Log.w(TAG, e);
268 return null;
269 }
270 }
271
272 private static byte[] generateSalt() {
273 SecureRandom random = new SecureRandom();
274 byte[] salt = new byte[16];
275 random.nextBytes(salt);
276
277 return salt;
278 }
279
280 private static int generateIterationCount(String passphrase, byte[] salt) {
281 int TARGET_ITERATION_TIME = 50; //ms
282 int MINIMUM_ITERATION_COUNT = 100; //default for low-end devices
283 int BENCHMARK_ITERATION_COUNT = 10000; //baseline starting iteration count
284
285 try {
286 PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, BENCHMARK_ITERATION_COUNT);
287 SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
288
289 long startTime = System.currentTimeMillis();
290 skf.generateSecret(keyspec);
291 long finishTime = System.currentTimeMillis();
292
293 int scaledIterationTarget = (int) (((double)BENCHMARK_ITERATION_COUNT / (double)(finishTime - startTime)) * TARGET_ITERATION_TIME);
294
295 if (scaledIterationTarget < MINIMUM_ITERATION_COUNT) return MINIMUM_ITERATION_COUNT;
296 else return scaledIterationTarget;
297 } catch (NoSuchAlgorithmException e) {
298 Log.w(TAG, e);
299 return MINIMUM_ITERATION_COUNT;
300 } catch (InvalidKeySpecException e) {
301 Log.w(TAG, e);
302 return MINIMUM_ITERATION_COUNT;
303 }
304 }
305
306 private static SecretKey getKeyFromPassphrase(String passphrase, byte[] salt, int iterations)
307 throws GeneralSecurityException
308 {
309 PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, iterations);
310 SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
311 return skf.generateSecret(keyspec);
312 }
313
314 private static Cipher getCipherFromPassphrase(String passphrase, byte[] salt, int iterations, int opMode)
315 throws GeneralSecurityException
316 {
317 SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
318 Cipher cipher = Cipher.getInstance(key.getAlgorithm());
319 cipher.init(opMode, key, new PBEParameterSpec(salt, iterations));
320
321 return cipher;
322 }
323
324 private static byte[] encryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
325 throws GeneralSecurityException
326 {
327 Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.ENCRYPT_MODE);
328 return cipher.doFinal(data);
329 }
330
331 private static byte[] decryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
332 throws GeneralSecurityException, IOException
333 {
334 Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.DECRYPT_MODE);
335 return cipher.doFinal(data);
336 }
337
338 private static Mac getMacForPassphrase(String passphrase, byte[] salt, int iterations)
339 throws GeneralSecurityException
340 {
341 SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
342 byte[] pbkdf2 = key.getEncoded();
343 SecretKeySpec hmacKey = new SecretKeySpec(pbkdf2, "HmacSHA1");
344 Mac hmac = Mac.getInstance("HmacSHA1");
345 hmac.init(hmacKey);
346
347 return hmac;
348 }
349
350 private static byte[] verifyMac(byte[] macSalt, int iterations, byte[] encryptedAndMacdData, String passphrase) throws InvalidPassphraseException, GeneralSecurityException, IOException {
351 Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
352
353 byte[] encryptedData = new byte[encryptedAndMacdData.length - hmac.getMacLength()];
354 System.arraycopy(encryptedAndMacdData, 0, encryptedData, 0, encryptedData.length);
355
356 byte[] givenMac = new byte[hmac.getMacLength()];
357 System.arraycopy(encryptedAndMacdData, encryptedAndMacdData.length-hmac.getMacLength(), givenMac, 0, givenMac.length);
358
359 byte[] localMac = hmac.doFinal(encryptedData);
360
361 if (Arrays.equals(givenMac, localMac)) return encryptedData;
362 else throw new InvalidPassphraseException("MAC Error");
363 }
364
365 private static byte[] macWithPassphrase(byte[] macSalt, int iterations, byte[] data, String passphrase) throws GeneralSecurityException {
366 Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
367 byte[] mac = hmac.doFinal(data);
368 byte[] result = new byte[data.length + mac.length];
369
370 System.arraycopy(data, 0, result, 0, data.length);
371 System.arraycopy(mac, 0, result, data.length, mac.length);
372
373 return result;
374 }
375
376 private static SharedPreferences getSharedPreferences(@NonNull Context context) {
377 if (preferences == null) {
378 preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
379 }
380 return preferences;
381 }
382}