package com.fox2code.mmm ;
import static com.fox2code.mmm.utils.IntentHelper.getActivity ;
import android.annotation.SuppressLint ;
import android.content.ComponentName ;
import android.content.Context ;
import android.content.Intent ;
import android.content.SharedPreferences ;
import android.content.pm.PackageManager ;
import android.content.res.Resources ;
import android.os.Bundle ;
import android.security.keystore.KeyGenParameterSpec ;
import android.security.keystore.KeyProperties ;
import android.util.Base64 ;
import android.view.View ;
import android.view.WindowManager ;
import androidx.annotation.NonNull ;
import androidx.appcompat.app.ActionBar ;
import androidx.fragment.app.FragmentActivity ;
import androidx.security.crypto.EncryptedFile ;
import androidx.security.crypto.MasterKey ;
import com.fox2code.foxcompat.app.FoxActivity ;
import com.fox2code.mmm.androidacy.AndroidacyRepoData ;
import com.fox2code.mmm.databinding.ActivitySetupBinding ;
import com.fox2code.mmm.repo.RepoManager ;
import com.fox2code.mmm.utils.realm.ReposList ;
import com.fox2code.rosettax.LanguageActivity ;
import com.fox2code.rosettax.LanguageSwitcher ;
import com.google.android.material.button.MaterialButton ;
import com.google.android.material.dialog.MaterialAlertDialogBuilder ;
import com.google.android.material.materialswitch.MaterialSwitch ;
import com.topjohnwu.superuser.internal.UiThreadHandler ;
import java.io.File ;
import java.io.FileNotFoundException ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.OutputStream ;
import java.nio.ByteBuffer ;
import java.nio.ByteOrder ;
import java.security.GeneralSecurityException ;
import java.security.InvalidAlgorithmParameterException ;
import java.security.InvalidKeyException ;
import java.security.KeyStore ;
import java.security.KeyStoreException ;
import java.security.NoSuchAlgorithmException ;
import java.security.NoSuchProviderException ;
import java.security.SecureRandom ;
import java.security.UnrecoverableKeyException ;
import java.security.cert.CertificateException ;
import java.util.Objects ;
import javax.crypto.BadPaddingException ;
import javax.crypto.Cipher ;
import javax.crypto.IllegalBlockSizeException ;
import javax.crypto.KeyGenerator ;
import javax.crypto.NoSuchPaddingException ;
import javax.crypto.SecretKey ;
import io.realm.Realm ;
import io.realm.RealmConfiguration ;
import timber.log.Timber ;
public class SetupActivity extends FoxActivity implements LanguageActivity {
MasterKey mainKeyAlias ;
@SuppressLint ( { "ApplySharedPref" , "RestrictedApi" } )
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
this . setTitle ( R . string . setup_title ) ;
// set action bar
ActionBar actionBar = getSupportActionBar ( ) ;
if ( actionBar ! = null ) {
// back button is close button
actionBar . setDisplayOptions ( ActionBar . DISPLAY_SHOW_TITLE | ActionBar . DISPLAY_USE_LOGO | ActionBar . DISPLAY_SHOW_HOME ) ;
actionBar . setLogo ( R . drawable . ic_foreground ) ;
// set title
actionBar . setTitle ( R . string . setup_title ) ;
actionBar . show ( ) ;
}
this . getWindow ( ) . setFlags ( WindowManager . LayoutParams . FLAG_TRANSLUCENT_NAVIGATION , 0 ) ;
createFiles ( ) ;
disableUpdateActivityForFdroidFlavor ( ) ;
// Set theme
SharedPreferences prefs = MainApplication . getSharedPreferences ( "mmm" ) ;
switch ( prefs . getString ( "theme" , "system" ) ) {
case "light" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet_Light ) ;
case "dark" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet_Dark ) ;
case "system" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet ) ;
case "black" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet_Black ) ;
case "transparent_light" - >
setTheme ( R . style . Theme_MagiskModuleManager_Transparent_Light ) ;
}
ActivitySetupBinding binding = ActivitySetupBinding . inflate ( getLayoutInflater ( ) ) ;
setContentView ( binding . getRoot ( ) ) ;
View view = binding . getRoot ( ) ;
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_background_update_check ) ) ) . setChecked ( BuildConfig . ENABLE_AUTO_UPDATER ) ;
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_crash_reporting ) ) ) . setChecked ( BuildConfig . DEFAULT_ENABLE_CRASH_REPORTING ) ;
// assert that both switches match the build config on debug builds
if ( BuildConfig . DEBUG ) {
assert ( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_background_update_check ) ) ) . isChecked ( ) = = BuildConfig . ENABLE_AUTO_UPDATER ;
assert ( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_crash_reporting ) ) ) . isChecked ( ) = = BuildConfig . DEFAULT_ENABLE_CRASH_REPORTING ;
}
// Repos are a little harder, as the enabled_repos build config is an arraylist
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_androidacy_repo ) ) ) . setChecked ( BuildConfig . ENABLED_REPOS . contains ( "androidacy_repo" ) ) ;
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_magisk_alt_repo ) ) ) . setChecked ( BuildConfig . ENABLED_REPOS . contains ( "magisk_alt_repo" ) ) ;
// On debug builds, log when a switch is toggled
if ( BuildConfig . DEBUG ) {
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_background_update_check ) ) ) . setOnCheckedChangeListener ( ( buttonView , isChecked ) - > Timber . i ( "Automatic update Check: %s" , isChecked ) ) ;
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_crash_reporting ) ) ) . setOnCheckedChangeListener ( ( buttonView , isChecked ) - > Timber . i ( "Crash Reporting: %s" , isChecked ) ) ;
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_androidacy_repo ) ) ) . setOnCheckedChangeListener ( ( buttonView , isChecked ) - > Timber . i ( "Androidacy Repo: %s" , isChecked ) ) ;
( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_magisk_alt_repo ) ) ) . setOnCheckedChangeListener ( ( buttonView , isChecked ) - > Timber . i ( "Magisk Alt Repo: %s" , isChecked ) ) ;
}
// Setup popup dialogue for the setup_theme_button
MaterialButton themeButton = view . findViewById ( R . id . setup_theme_button ) ;
themeButton . setOnClickListener ( v - > {
// Create a new dialog for the theme picker
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder ( this ) ;
builder . setTitle ( R . string . setup_theme_title ) ;
// Create a new array of theme names (system, light, dark, black, transparent light)
String [ ] themeNames = new String [ ] { getString ( R . string . theme_system ) , getString ( R . string . theme_light ) , getString ( R . string . theme_dark ) , getString ( R . string . theme_black ) , getString ( R . string . theme_transparent_light ) } ;
// Create a new array of theme values (system, light, dark, black, transparent_light)
String [ ] themeValues = new String [ ] { "system" , "light" , "dark" , "black" , "transparent_light" } ;
// if pref_theme is set, check the relevant theme_* menu item, otherwise check the default (theme_system)
String prefTheme = prefs . getString ( "pref_theme" , "system" ) ;
int checkedItem = 0 ;
switch ( prefTheme ) {
case "system" :
break ;
case "light" :
checkedItem = 1 ;
break ;
case "dark" :
checkedItem = 2 ;
break ;
case "black" :
checkedItem = 3 ;
break ;
case "transparent_light" :
checkedItem = 4 ;
break ;
}
builder . setCancelable ( true ) ;
// Create the dialog
builder . setSingleChoiceItems ( themeNames , checkedItem , ( dialog , which ) - > {
// Set the theme
prefs . edit ( ) . putString ( "pref_theme" , themeValues [ which ] ) . commit ( ) ;
// Dismiss the dialog
dialog . dismiss ( ) ;
// Set the theme
UiThreadHandler . handler . postDelayed ( ( ) - > {
switch ( prefs . getString ( "pref_theme" , "system" ) ) {
case "light" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet_Light ) ;
case "dark" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet_Dark ) ;
case "system" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet ) ;
case "black" - > setTheme ( R . style . Theme_MagiskModuleManager_Monet_Black ) ;
case "transparent_light" - >
setTheme ( R . style . Theme_MagiskModuleManager_Transparent_Light ) ;
}
// restart the activity because switching to transparent pisses the rendering engine off
Intent intent = new Intent ( this , SetupActivity . class ) ;
finish ( ) ;
// ensure intent originates from the same package
intent . setPackage ( getPackageName ( ) ) ;
startActivity ( intent ) ;
} , 100 ) ;
} ) ;
builder . show ( ) ;
} ) ;
// Setup language selector
MaterialButton languageSelector = view . findViewById ( R . id . setup_language_button ) ;
languageSelector . setOnClickListener ( preference - > {
LanguageSwitcher ls = new LanguageSwitcher ( Objects . requireNonNull ( getActivity ( this ) ) ) ;
ls . setSupportedStringLocales ( MainApplication . supportedLocales ) ;
ls . showChangeLanguageDialog ( ( FragmentActivity ) getActivity ( this ) ) ;
} ) ;
// Set up the buttons
// Setup button
MaterialButton setupButton = view . findViewById ( R . id . setup_continue ) ;
setupButton . setOnClickListener ( v - > {
// Set first launch to false
// get instance of editor
SharedPreferences . Editor editor = prefs . edit ( ) ;
editor . putString ( "last_shown_setup" , "v1" ) ;
// Set the Automatic update check pref
editor . putBoolean ( "pref_background_update_check" , ( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_background_update_check ) ) ) . isChecked ( ) ) ;
// Set the crash reporting pref
editor . putBoolean ( "pref_crash_reporting" , ( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_crash_reporting ) ) ) . isChecked ( ) ) ;
// Set the repos in the ReposList realm db
RealmConfiguration realmConfig = new RealmConfiguration . Builder ( ) . name ( "ReposList.realm" ) . directory ( MainApplication . getINSTANCE ( ) . getDataDirWithPath ( "realms" ) ) . schemaVersion ( 1 ) . encryptionKey ( MainApplication . getINSTANCE ( ) . getExistingKey ( ) ) . allowQueriesOnUiThread ( true ) . allowWritesOnUiThread ( true ) . build ( ) ;
boolean androidacyRepo = ( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_androidacy_repo ) ) ) . isChecked ( ) ;
boolean magiskAltRepo = ( ( MaterialSwitch ) Objects . requireNonNull ( view . findViewById ( R . id . setup_magisk_alt_repo ) ) ) . isChecked ( ) ;
Realm realm = Realm . getInstance ( realmConfig ) ;
Objects . requireNonNull ( realm . where ( ReposList . class ) . equalTo ( "id" , "androidacy_repo" ) . findFirst ( ) ) . setEnabled ( androidacyRepo ) ;
Objects . requireNonNull ( realm . where ( ReposList . class ) . equalTo ( "id" , "magisk_alt_repo" ) . findFirst ( ) ) . setEnabled ( magiskAltRepo ) ;
// commit the changes
realm . commitTransaction ( ) ;
realm . close ( ) ;
// Commit the changes
editor . commit ( ) ;
// Sleep for 1 second to allow the user to see the changes
try {
Thread . sleep ( 500 ) ;
} catch ( InterruptedException e ) {
Thread . currentThread ( ) . interrupt ( ) ;
}
// Log the changes if debug
if ( BuildConfig . DEBUG ) {
Timber . d ( "Automatic update check: %s" , prefs . getBoolean ( "pref_background_update_check" , false ) ) ;
Timber . i ( "Crash reporting: %s" , prefs . getBoolean ( "pref_crash_reporting" , false ) ) ;
}
// Restart the activity
MainActivity . doSetupRestarting = true ;
Intent intent = new Intent ( this , MainActivity . class ) ;
startActivity ( intent ) ;
finish ( ) ;
} ) ;
// Cancel button
MaterialButton cancelButton = view . findViewById ( R . id . setup_cancel ) ;
cancelButton . setText ( R . string . cancel ) ;
cancelButton . setOnClickListener ( v - > {
// Set first launch to false and restart the activity
prefs . edit ( ) . putString ( "last_shown_setup" , "v1" ) . commit ( ) ;
MainActivity . doSetupRestarting = true ;
Intent intent = new Intent ( this , MainActivity . class ) ;
startActivity ( intent ) ;
finish ( ) ;
} ) ;
}
@Override
public Resources . Theme getTheme ( ) {
Resources . Theme theme = super . getTheme ( ) ;
// Set the theme
SharedPreferences prefs = MainApplication . getSharedPreferences ( "mmm" ) ;
switch ( prefs . getString ( "pref_theme" , "system" ) ) {
case "light" - > theme . applyStyle ( R . style . Theme_MagiskModuleManager_Monet_Light , true ) ;
case "dark" - > theme . applyStyle ( R . style . Theme_MagiskModuleManager_Monet_Dark , true ) ;
case "system" - > theme . applyStyle ( R . style . Theme_MagiskModuleManager_Monet , true ) ;
case "black" - > theme . applyStyle ( R . style . Theme_MagiskModuleManager_Monet_Black , true ) ;
case "transparent_light" - >
theme . applyStyle ( R . style . Theme_MagiskModuleManager_Transparent_Light , true ) ;
}
return theme ;
}
@Override
@SuppressLint ( { "InlinedApi" , "RestrictedApi" } )
public void refreshRosettaX ( ) {
// refresh app language
runOnUiThread ( ( ) - > {
// refresh activity
Intent intent = new Intent ( this , SetupActivity . class ) ;
finish ( ) ;
startActivity ( intent ) ;
} ) ;
}
// creates the realm database
private void createRealmDatabase ( ) {
Timber . d ( "Creating Realm databases" ) ;
// create encryption key
Timber . d ( "Creating encryption key" ) ;
// generate the encryption key and store it in the prefs
byte [ ] encryptionKey = getNewKey ( ) ;
// create the realm database for ReposList
// next, create the realm database for ReposList
RealmConfiguration config2 = new RealmConfiguration . Builder ( ) . name ( "ReposList.realm" ) . allowQueriesOnUiThread ( true ) . allowWritesOnUiThread ( true ) . directory ( MainApplication . getINSTANCE ( ) . getDataDirWithPath ( "realms" ) ) . schemaVersion ( 1 ) . encryptionKey ( MainApplication . getINSTANCE ( ) . getExistingKey ( ) ) . encryptionKey ( encryptionKey ) . build ( ) ;
// get the instance
Realm . getInstanceAsync ( config2 , new Realm . Callback ( ) {
@Override
public void onSuccess ( @NonNull Realm realm1 ) {
// create androidacy_repo and magisk_alt_repo if they don't exist under ReposList
// each has id, name, donate, website, support, enabled, and lastUpdate and name
// create androidacy_repo
realm1 . beginTransaction ( ) ;
if ( realm1 . where ( ReposList . class ) . equalTo ( "id" , "androidacy_repo" ) . findFirst ( ) = = null ) {
// cant use createObject because it crashes because reasons. use copyToRealm instead
ReposList androidacy_repo = realm1 . createObject ( ReposList . class , "androidacy_repo" ) ;
androidacy_repo . setName ( "Androidacy Repo" ) ;
androidacy_repo . setDonate ( AndroidacyRepoData . getInstance ( ) . getDonate ( ) ) ;
androidacy_repo . setSupport ( AndroidacyRepoData . getInstance ( ) . getSupport ( ) ) ;
androidacy_repo . setSubmitModule ( AndroidacyRepoData . getInstance ( ) . getSubmitModule ( ) ) ;
androidacy_repo . setUrl ( RepoManager . ANDROIDACY_MAGISK_REPO_ENDPOINT ) ;
androidacy_repo . setEnabled ( true ) ;
androidacy_repo . setLastUpdate ( 0 ) ;
androidacy_repo . setWebsite ( RepoManager . ANDROIDACY_MAGISK_REPO_HOMEPAGE ) ;
// now copy the data from the data class to the realm object using copyToRealmOrUpdate
realm1 . insertOrUpdate ( androidacy_repo ) ;
}
// create magisk_alt_repo
if ( realm1 . where ( ReposList . class ) . equalTo ( "id" , "magisk_alt_repo" ) . findFirst ( ) = = null ) {
ReposList magisk_alt_repo = realm1 . createObject ( ReposList . class , "magisk_alt_repo" ) ;
magisk_alt_repo . setName ( "Magisk Alt Repo" ) ;
magisk_alt_repo . setDonate ( null ) ;
magisk_alt_repo . setWebsite ( RepoManager . MAGISK_ALT_REPO_HOMEPAGE ) ;
magisk_alt_repo . setSupport ( null ) ;
magisk_alt_repo . setEnabled ( true ) ;
magisk_alt_repo . setUrl ( RepoManager . MAGISK_ALT_REPO ) ;
magisk_alt_repo . setSubmitModule ( RepoManager . MAGISK_ALT_REPO_HOMEPAGE + "/submission" ) ;
magisk_alt_repo . setLastUpdate ( 0 ) ;
// commit the changes
realm1 . insertOrUpdate ( magisk_alt_repo ) ;
}
realm1 . commitTransaction ( ) ;
}
} ) ;
}
public void createFiles ( ) {
try {
String cookieFileName = "cookies" ;
// initial set of cookies, only really used to create the keypair and encrypted file
String initialCookie = "is_foxmmm=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=production-api.androidacy.com; SameSite=None; Secure;|foxmmm_version=" + BuildConfig . VERSION_CODE + "; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=production-api.androidacy.com; SameSite=None; Secure;" ;
Context context = getApplicationContext ( ) ;
mainKeyAlias = new MasterKey . Builder ( context ) . setKeyScheme ( MasterKey . KeyScheme . AES256_GCM ) . build ( ) ;
EncryptedFile encryptedFile = new EncryptedFile . Builder ( context , new File ( MainApplication . getINSTANCE ( ) . getFilesDir ( ) , cookieFileName ) , mainKeyAlias , EncryptedFile . FileEncryptionScheme . AES256_GCM_HKDF_4KB ) . build ( ) ;
InputStream inputStream ;
try {
inputStream = encryptedFile . openFileInput ( ) ;
} catch ( FileNotFoundException e ) {
Timber . d ( "Cookie file not found, creating new file" ) ;
OutputStream outputStream = encryptedFile . openFileOutput ( ) ;
outputStream . write ( initialCookie . getBytes ( ) ) ;
outputStream . close ( ) ;
outputStream . flush ( ) ;
inputStream = encryptedFile . openFileInput ( ) ;
}
byte [ ] buffer = new byte [ 1024 ] ;
int bytesRead ;
StringBuilder outputString = new StringBuilder ( ) ;
while ( ( bytesRead = inputStream . read ( buffer ) ) ! = - 1 ) {
outputString . append ( new String ( buffer , 0 , bytesRead ) ) ;
}
inputStream . close ( ) ;
if ( outputString . toString ( ) . isEmpty ( ) ) {
Timber . d ( "Cookie file is empty, writing initial cookie" ) ;
OutputStream outputStream = encryptedFile . openFileOutput ( ) ;
outputStream . write ( initialCookie . getBytes ( ) ) ;
outputStream . close ( ) ;
outputStream . flush ( ) ;
}
} catch ( GeneralSecurityException | IOException e ) {
Timber . e ( e ) ;
}
// we literally only use these to create the http cache folders
File httpCacheDir = MainApplication . getINSTANCE ( ) . getDataDirWithPath ( "cache/WebView/Default/HTTP Cache/Code Cache/js" ) ;
File httpCacheDir2 = MainApplication . getINSTANCE ( ) . getDataDirWithPath ( "cache/WebView/Default/HTTP Cache/Code Cache/wasm" ) ;
if ( ! httpCacheDir . exists ( ) ) {
if ( httpCacheDir . mkdirs ( ) ) {
Timber . d ( "Created http cache dir" ) ;
}
}
if ( ! httpCacheDir2 . exists ( ) ) {
if ( httpCacheDir2 . mkdirs ( ) ) {
Timber . d ( "Created http cache dir" ) ;
}
}
createRealmDatabase ( ) ;
}
@SuppressWarnings ( "ConstantConditions" )
public void disableUpdateActivityForFdroidFlavor ( ) {
if ( BuildConfig . FLAVOR . equals ( "fdroid" ) ) {
Timber . d ( "Disabling update activity for fdroid flavor" ) ;
// disable update activity through package manager
PackageManager pm = getPackageManager ( ) ;
ComponentName componentName = new ComponentName ( this , UpdateActivity . class ) ;
pm . setComponentEnabledSetting ( componentName , PackageManager . COMPONENT_ENABLED_STATE_DISABLED , PackageManager . DONT_KILL_APP ) ;
}
}
@SuppressLint ( "NewApi" )
public byte [ ] getNewKey ( ) {
if ( MainApplication . getSharedPreferences ( "mmm" ) . getBoolean ( "keygen" , false ) ) {
Timber . d ( "Key already generated, returning" ) ;
return MainApplication . getINSTANCE ( ) . getExistingKey ( ) ;
}
// open a connection to the android keystore
KeyStore keyStore ;
try {
keyStore = KeyStore . getInstance ( "AndroidKeyStore" ) ;
keyStore . load ( null ) ;
} catch ( java . security . KeyStoreException | NoSuchAlgorithmException | CertificateException |
IOException e ) {
throw new RuntimeException ( e ) ;
}
// create a securely generated random asymmetric RSA key
byte [ ] realmKey = new byte [ Realm . ENCRYPTION_KEY_LENGTH ] ;
new SecureRandom ( ) . nextBytes ( realmKey ) ;
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
Cipher cipher ;
try {
cipher = Cipher . getInstance ( KeyProperties . KEY_ALGORITHM_AES + "/" + KeyProperties . BLOCK_MODE_CBC + "/" + KeyProperties . ENCRYPTION_PADDING_PKCS7 ) ;
} catch ( NoSuchAlgorithmException | NoSuchPaddingException e ) {
throw new RuntimeException ( e ) ;
}
// generate secret key
KeyGenerator keyGenerator ;
try {
keyGenerator = KeyGenerator . getInstance ( KeyProperties . KEY_ALGORITHM_AES , "AndroidKeyStore" ) ;
} catch ( NoSuchAlgorithmException | NoSuchProviderException e ) {
throw new RuntimeException ( e ) ;
}
KeyGenParameterSpec keySpec = new KeyGenParameterSpec . Builder ( "realm_key" , KeyProperties . PURPOSE_ENCRYPT | KeyProperties . PURPOSE_DECRYPT ) . setBlockModes ( KeyProperties . BLOCK_MODE_CBC ) . setEncryptionPaddings ( KeyProperties . ENCRYPTION_PADDING_PKCS7 ) . setUserAuthenticationRequired ( false ) . build ( ) ;
try {
keyGenerator . init ( keySpec ) ;
} catch ( InvalidAlgorithmParameterException e ) {
throw new RuntimeException ( e ) ;
}
keyGenerator . generateKey ( ) ;
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
byte [ ] initializationVector ;
byte [ ] encryptedKeyForRealm ;
try {
SecretKey secretKey = ( SecretKey ) keyStore . getKey ( "realm_key" , null ) ;
cipher . init ( Cipher . ENCRYPT_MODE , secretKey ) ;
encryptedKeyForRealm = cipher . doFinal ( realmKey ) ;
initializationVector = cipher . getIV ( ) ;
} catch ( InvalidKeyException | UnrecoverableKeyException | NoSuchAlgorithmException |
KeyStoreException | BadPaddingException | IllegalBlockSizeException e ) {
throw new RuntimeException ( e ) ;
}
// keep the encrypted key in shared preferences
// to persist it across application runs
byte [ ] initializationVectorAndEncryptedKey = new byte [ Integer . BYTES + initializationVector . length + encryptedKeyForRealm . length ] ;
ByteBuffer buffer = ByteBuffer . wrap ( initializationVectorAndEncryptedKey ) ;
buffer . order ( ByteOrder . BIG_ENDIAN ) ;
buffer . putInt ( initializationVector . length ) ;
buffer . put ( initializationVector ) ;
buffer . put ( encryptedKeyForRealm ) ;
MainApplication . getSharedPreferences ( "realm_key" ) . edit ( ) . putString ( "iv_and_encrypted_key" , Base64 . encodeToString ( initializationVectorAndEncryptedKey , Base64 . NO_WRAP ) ) . apply ( ) ;
MainApplication . getSharedPreferences ( "mmm" ) . edit ( ) . putBoolean ( "keygen" , true ) . apply ( ) ;
return realmKey ; // pass to a realm configuration via encryptionKey()
}
}