That fuck shit the fascists are using
at master 382 lines 16 kB view raw
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}