From e190270ad655a9edc4fccddb405e92f1eb7f42ee Mon Sep 17 00:00:00 2001 From: Bervianto Leo Pratama Date: Sat, 26 Jul 2025 15:51:28 +0700 Subject: [PATCH 1/5] Rename .java to .kt --- .../ecceg_rsa_app/{ECCEGActivity.java => ECCEGActivity.kt} | 0 .../ecceg_rsa_app/{MainActivity.java => MainActivity.kt} | 0 .../ecceg_rsa_app/{RSAActivity.java => RSAActivity.kt} | 0 .../{RSAGenerateKeyFragment.java => RSAGenerateKeyFragment.kt} | 0 .../id/my/berviantoleo/ecceg_rsa_app/lib/ecc/{ECC.java => ECC.kt} | 0 .../berviantoleo/ecceg_rsa_app/lib/ecc/{ECCEG.java => ECCEG.kt} | 0 .../ecceg_rsa_app/lib/ecc/{ECCEGMain.java => ECCEGMain.kt} | 0 .../my/berviantoleo/ecceg_rsa_app/lib/ecc/{Pair.java => Pair.kt} | 0 .../berviantoleo/ecceg_rsa_app/lib/ecc/{Point.java => Point.kt} | 0 .../id/my/berviantoleo/ecceg_rsa_app/lib/rsa/{RSA.java => RSA.kt} | 0 .../ecceg_rsa_app/utils/{FileUtils.java => FileUtils.kt} | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/{ECCEGActivity.java => ECCEGActivity.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/{MainActivity.java => MainActivity.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/{RSAActivity.java => RSAActivity.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/{RSAGenerateKeyFragment.java => RSAGenerateKeyFragment.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/{ECC.java => ECC.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/{ECCEG.java => ECCEG.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/{ECCEGMain.java => ECCEGMain.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/{Pair.java => Pair.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/{Point.java => Point.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/{RSA.java => RSA.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/utils/{FileUtils.java => FileUtils.kt} (100%) diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/utils/FileUtils.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/utils/FileUtils.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/utils/FileUtils.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/utils/FileUtils.kt From 9e70ebf857b6cb820148442688a4524269b3178d Mon Sep 17 00:00:00 2001 From: Bervianto Leo Pratama Date: Sat, 26 Jul 2025 15:51:29 +0700 Subject: [PATCH 2/5] feat: initial upgrade --- .idea/AndroidProjectSystem.xml | 6 + .idea/appInsightsSettings.xml | 40 + .idea/caches/deviceStreaming.xml | 835 ++++++++++++++++++ .idea/compiler.xml | 2 +- .idea/deploymentTargetSelector.xml | 10 + .idea/gradle.xml | 2 +- .idea/misc.xml | 12 +- .idea/modules.xml | 12 - .idea/other.xml | 318 +++++++ .idea/runConfigurations.xml | 17 + app/build.gradle | 16 +- .../ecceg_rsa_app/ECCEGActivity.kt | 43 +- .../ecceg_rsa_app/MainActivity.kt | 38 +- .../berviantoleo/ecceg_rsa_app/RSAActivity.kt | 48 +- .../fragment/RSAEncryptFragment.java | 76 +- .../fragment/RSAGenerateKeyFragment.kt | 203 ++--- .../berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt | 300 ++++--- .../ecceg_rsa_app/lib/ecc/ECCEG.kt | 177 ++-- .../ecceg_rsa_app/lib/ecc/ECCEGMain.kt | 171 ++-- .../ecceg_rsa_app/lib/ecc/Pair.kt | 34 +- .../ecceg_rsa_app/lib/ecc/Point.kt | 24 +- .../berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt | 402 +++++---- .../ecceg_rsa_app/utils/FileUtils.kt | 206 ++--- app/src/main/res/layout/activity_main.xml | 26 +- .../main/res/layout/fragment_eccegdecrypt.xml | 42 +- .../main/res/layout/fragment_eccegencrypt.xml | 40 +- .../res/layout/fragment_ecceggenerate_key.xml | 13 +- .../main/res/layout/fragment_rsadecrypt.xml | 39 +- .../main/res/layout/fragment_rsaencrypt.xml | 39 +- .../res/layout/fragment_rsagenerate_key.xml | 13 +- .../main/res/layout/layout_loading_dialog.xml | 18 + build.gradle | 2 +- gradle.properties | 2 +- settings.gradle | 8 + 34 files changed, 2222 insertions(+), 1012 deletions(-) create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/appInsightsSettings.xml create mode 100644 .idea/caches/deviceStreaming.xml create mode 100644 .idea/deploymentTargetSelector.xml delete mode 100644 .idea/modules.xml create mode 100644 .idea/other.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/src/main/res/layout/layout_loading_dialog.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..26eb913 --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..9aaec77 --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,835 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b589d56..b86273d 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 0897082..639c779 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,6 +4,7 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 5f87066..21ac703 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,11 +1,11 @@ - + - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 98a2ded..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 0000000..94c96f6 --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,318 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index de1957e..71b8991 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,13 +4,13 @@ apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' android { - compileSdk 34 + compileSdk 36 defaultConfig { applicationId "id.my.berviantoleo.ecceg_rsa_app" - minSdkVersion 21 - targetSdkVersion 34 - versionCode 3 - versionName "1.2" + minSdkVersion 23 + targetSdkVersion 36 + versionCode 4 + versionName "1.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true } @@ -33,15 +33,13 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.2.1' implementation "androidx.core:core-ktx:1.16.0" implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'cc.cloudist.acplibrary:library:1.2.1' implementation 'com.android.support:multidex:1.0.3' implementation 'com.jakewharton:butterknife:10.2.3' - implementation 'com.github.medyo:fancybuttons:1.9.1' implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.firebase:firebase-crashlytics:20.0.0' implementation 'com.google.firebase:firebase-analytics:23.0.0' - implementation 'com.obsez.android.lib.filechooser:filechooser:1.2.0' - implementation 'gun0912.ted:tedpermission:2.2.3' + implementation 'io.github.ParkSangGwon:tedpermission-normal:3.4.2' + implementation 'io.github.chochanaresh:filepicker:0.5.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt index 3e0eea4..bca89f3 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt @@ -1,29 +1,28 @@ -package id.my.berviantoleo.ecceg_rsa_app; +package id.my.berviantoleo.ecceg_rsa_app -import android.os.Bundle; - -import id.my.berviantoleo.ecceg_rsa_app.adapter.ECCEGPagerAdapter; -import com.google.android.material.tabs.TabLayout; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.viewpager.widget.ViewPager; -import butterknife.BindView; -import butterknife.ButterKnife; - -public class ECCEGActivity extends AppCompatActivity { +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.viewpager.widget.ViewPager +import butterknife.BindView +import butterknife.ButterKnife +import com.google.android.material.tabs.TabLayout +import id.my.berviantoleo.ecceg_rsa_app.adapter.ECCEGPagerAdapter +class ECCEGActivity : AppCompatActivity() { + @JvmField @BindView(R.id.container_ecceg) - protected ViewPager mViewPager; + var mViewPager: ViewPager? = null + + @JvmField @BindView(R.id.tabs_ecceg) - protected TabLayout tabLayout; + var tabLayout: TabLayout? = null - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_ecceg); - ButterKnife.bind(this); - ECCEGPagerAdapter mSectionsPagerAdapter = new ECCEGPagerAdapter(getSupportFragmentManager()); - mViewPager.setAdapter(mSectionsPagerAdapter); - tabLayout.setupWithViewPager(mViewPager); + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_ecceg) + ButterKnife.bind(this) + val mSectionsPagerAdapter = ECCEGPagerAdapter(supportFragmentManager) + mViewPager!!.adapter = mSectionsPagerAdapter + tabLayout!!.setupWithViewPager(mViewPager) } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt index 00d69ee..066b6e7 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt @@ -1,31 +1,27 @@ -package id.my.berviantoleo.ecceg_rsa_app; +package id.my.berviantoleo.ecceg_rsa_app -import android.content.Intent; -import android.os.Bundle; +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import butterknife.ButterKnife +import butterknife.OnClick -import androidx.appcompat.app.AppCompatActivity; - -import butterknife.ButterKnife; -import butterknife.OnClick; - -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - ButterKnife.bind(this); +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + ButterKnife.bind(this) } @OnClick(R.id.rsa_button) - void launchRSA() { - Intent intent = new Intent(this, RSAActivity.class); - startActivity(intent); + fun launchRSA() { + val intent = Intent(this, RSAActivity::class.java) + startActivity(intent) } @OnClick(R.id.ecceg_button) - void launchECCEG() { - Intent intent = new Intent(this, ECCEGActivity.class); - startActivity(intent); + fun launchECCEG() { + val intent = Intent(this, ECCEGActivity::class.java) + startActivity(intent) } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt index 47c5204..6c5139e 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt @@ -1,35 +1,29 @@ -package id.my.berviantoleo.ecceg_rsa_app; +package id.my.berviantoleo.ecceg_rsa_app -import android.os.Bundle; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.viewpager.widget.ViewPager; - -import com.google.android.material.tabs.TabLayout; - -import butterknife.BindView; -import butterknife.ButterKnife; -import id.my.berviantoleo.ecceg_rsa_app.adapter.RSAPagerAdapter; - -public class RSAActivity extends AppCompatActivity { +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.viewpager.widget.ViewPager +import butterknife.BindView +import butterknife.ButterKnife +import com.google.android.material.tabs.TabLayout +import id.my.berviantoleo.ecceg_rsa_app.adapter.RSAPagerAdapter +class RSAActivity : AppCompatActivity() { /** - * The {@link ViewPager} that will host the section contents. + * The [ViewPager] that will host the section contents. */ @BindView(R.id.container) - protected ViewPager mViewPager; - @BindView(R.id.tabs) - protected TabLayout tabLayout; + var mViewPager: ViewPager? = null - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_rsa); - ButterKnife.bind(this); - RSAPagerAdapter mSectionsPagerAdapter = new RSAPagerAdapter(getSupportFragmentManager()); - mViewPager.setAdapter(mSectionsPagerAdapter); - tabLayout.setupWithViewPager(mViewPager); + @BindView(R.id.tabs) + var tabLayout: TabLayout? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_rsa) + ButterKnife.bind(this) + val mSectionsPagerAdapter = RSAPagerAdapter(supportFragmentManager) + mViewPager!!.adapter = mSectionsPagerAdapter + tabLayout!!.setupWithViewPager(mViewPager) } - - } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.java index 36f0ca4..023e306 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.java +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.java @@ -1,9 +1,11 @@ package id.my.berviantoleo.ecceg_rsa_app.fragment; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,19 +14,23 @@ import id.my.berviantoleo.ecceg_rsa_app.R; import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA; import com.google.android.material.textfield.TextInputEditText; -import com.obsez.android.lib.filechooser.ChooserDialog; +import com.nareshchocha.filepickerlibrary.FilePickerResultContracts; +import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig; +import com.nareshchocha.filepickerlibrary.models.FilePickerResult; import java.io.File; import java.lang.ref.WeakReference; import java.math.BigInteger; import java.util.Objects; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import cc.cloudist.acplibrary.ACProgressFlower; import static android.os.Environment.getExternalStorageDirectory; @@ -55,7 +61,7 @@ public class RSAEncryptFragment extends Fragment { @BindView(R.id.timeValue) protected TextInputEditText timeValue; private String keyPath; - private ACProgressFlower loadingView; + private AlertDialog dialog; private long startTime; public RSAEncryptFragment() { @@ -72,40 +78,46 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_rsaencrypt, container, false); ButterKnife.bind(this, view); - loadingView = new ACProgressFlower.Builder(getContext()).build(); - loadingView.setCanceledOnTouchOutside(false); - loadingView.setCancelable(false); + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); + builder.setCancelable(false); // if you want user to wait for some process to finish, + builder.setView(R.layout.layout_loading_dialog); + dialog = builder.create(); return view; } @OnClick(R.id.open_public_button) void openPublicKey() { - new ChooserDialog(getActivity()) - .withFilter(false, false, "pub") - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> { - keyPath = pathFile.getPath(); - loadingView.show(); - new OpenKey(RSAEncryptFragment.this).execute(keyPath); - }) - .build() - .show(); + ActivityResultLauncher launcher = registerForActivityResult(new FilePickerResultContracts.PickDocumentFile(), (ActivityResultCallback) result -> { + String errorMessage = result.getErrorMessage(); + if (errorMessage != null) { + Log.e("Picker", errorMessage); + } else { + keyPath = result.getSelectedFilePath(); + dialog.show(); + new OpenKey(RSAEncryptFragment.this).execute(keyPath); + } + }); + DocumentFilePickerConfig config = new DocumentFilePickerConfig(); + launcher.launch(config); } @OnClick(R.id.select_encrypt_file_button) void openFileEncrypt() { - new ChooserDialog(getActivity()) - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> { - encryptLoc.setText(pathFile.getPath()); - inputSize.setText(String.valueOf(pathFile.length())); - loadingView.show(); - new SetInput(RSAEncryptFragment.this).execute(pathFile.getPath()); - }) - .build() - .show(); + ActivityResultLauncher launcher = registerForActivityResult(new FilePickerResultContracts.PickDocumentFile(), (ActivityResultCallback) result -> { + String errorMessage = result.getErrorMessage(); + if (errorMessage != null) { + Log.e("Picker", errorMessage); + } else { + String path = result.getSelectedFilePath(); + if (path == null) return; + encryptLoc.setText(path); + inputSize.setText(String.valueOf(path.length())); + dialog.show(); + new SetInput(RSAEncryptFragment.this).execute(path); + } + }); + DocumentFilePickerConfig config = new DocumentFilePickerConfig(); + launcher.launch(config); } @OnClick(R.id.encrypt_button) @@ -114,7 +126,7 @@ void encrypt() { !Objects.requireNonNull(encryptLocValue.getText()).toString().equalsIgnoreCase("") && !Objects.requireNonNull(nModulus.getText()).toString().equalsIgnoreCase("") && !Objects.requireNonNull(publicKey.getText()).toString().equalsIgnoreCase("")) { - loadingView.show(); + dialog.show(); File file = Environment.getExternalStorageDirectory(); File location = new File(file, "RSA/"); if (!location.exists()) { @@ -148,7 +160,7 @@ protected void onPostExecute(String hex) { outputValue.setText(hex); File file = new File(Environment.getExternalStorageDirectory().getPath() + "/RSA/" + Objects.requireNonNull(encryptLocValue.getText()).toString()); outputSize.setText(String.valueOf(file.length())); - loadingView.dismiss(); + dialog.dismiss(); Toast.makeText(getActivity(), "Finished Encrypt", Toast.LENGTH_SHORT).show(); } } @@ -172,7 +184,7 @@ protected void onPostExecute(String s) { String pubKey = key[1]; publicKey.setText(pubKey); nModulus.setText(N); - loadingView.dismiss(); + dialog.dismiss(); } } @@ -197,7 +209,7 @@ protected String doInBackground(String... strings) { @Override protected void onPostExecute(String s) { inputValue.setText(s); - loadingView.dismiss(); + dialog.dismiss(); } } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt index b1dc565..0f15c5b 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt @@ -1,126 +1,129 @@ -package id.my.berviantoleo.ecceg_rsa_app.fragment; - -import android.Manifest; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import id.my.berviantoleo.ecceg_rsa_app.R; -import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA; -import com.google.android.material.textfield.TextInputEditText; -import com.gun0912.tedpermission.PermissionListener; -import com.gun0912.tedpermission.TedPermission; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.Objects; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cc.cloudist.acplibrary.ACProgressFlower; - - -public class RSAGenerateKeyFragment extends Fragment { - +package id.my.berviantoleo.ecceg_rsa_app.fragment + +import android.Manifest +import android.os.AsyncTask +import android.os.Bundle +import android.os.Environment +import android.text.Editable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import butterknife.BindView +import butterknife.ButterKnife +import butterknife.OnClick +import com.google.android.material.textfield.TextInputEditText +import com.gun0912.tedpermission.PermissionListener +import com.gun0912.tedpermission.normal.TedPermission +import id.my.berviantoleo.ecceg_rsa_app.R +import id.my.berviantoleo.ecceg_rsa_app.fragment.RSAGenerateKeyFragment +import java.io.File +import java.lang.ref.WeakReference +import java.util.Objects + +class RSAGenerateKeyFragment : Fragment() { + @JvmField @BindView(R.id.public_location_save) - protected TextInputEditText publicLocation; - @BindView(R.id.private_location_save) - protected TextInputEditText privateLocation; - @BindView(R.id.byte_size) - protected TextInputEditText byteSize; - private ACProgressFlower loadingView; - private PermissionListener extract; + var publicLocation: TextInputEditText? = null - public RSAGenerateKeyFragment() { - // Required empty public constructor - } + @JvmField + @BindView(R.id.private_location_save) + var privateLocation: TextInputEditText? = null - public static RSAGenerateKeyFragment newInstance() { - return new RSAGenerateKeyFragment(); - } + @JvmField + @BindView(R.id.byte_size) + var byteSize: TextInputEditText? = null + private var dialog: AlertDialog? = null + private var extract: PermissionListener? = null - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) } - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_rsagenerate_key, container, false); - ButterKnife.bind(this, view); - loadingView = new ACProgressFlower.Builder(getContext()).build(); - loadingView.setCanceledOnTouchOutside(false); - loadingView.setCancelable(false); - extract = new PermissionListener() { - @Override - public void onPermissionGranted() { - File location = Environment.getExternalStorageDirectory(); - File newLocation = new File(location, "RSA/"); + val view = inflater.inflate(R.layout.fragment_rsagenerate_key, container, false) + ButterKnife.bind(this, view) + val builder = AlertDialog.Builder(requireContext()) + builder.setCancelable(false) // if you want user to wait for some process to finish, + builder.setView(R.layout.layout_loading_dialog) + dialog = builder.create() + extract = object : PermissionListener { + override fun onPermissionGranted() { + val location = Environment.getExternalStorageDirectory() + val newLocation = File(location, "RSA/") if (!newLocation.exists()) { - newLocation.mkdir(); + newLocation.mkdir() } - loadingView.show(); - new GenerateKey(RSAGenerateKeyFragment.this).execute(Objects.requireNonNull(byteSize.getText()).toString(), newLocation.getAbsolutePath() + "/" + privateLocation.getText().toString(), - newLocation.getAbsolutePath() + "/" + Objects.requireNonNull(publicLocation.getText()).toString()); + dialog!!.show() + id.my.berviantoleo.ecceg_rsa_app.fragment.RSAGenerateKeyFragment.GenerateKey(this@RSAGenerateKeyFragment) + .execute( + Objects.requireNonNull( + byteSize!!.text + ).toString(), + newLocation.absolutePath + "/" + privateLocation!!.text.toString(), + newLocation.absolutePath + "/" + Objects.requireNonNull( + publicLocation!!.text + ).toString() + ) } - @Override - public void onPermissionDenied(List deniedPermissions) { - Toast.makeText(getContext(), "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show(); + override fun onPermissionDenied(deniedPermissions: List) { + Toast.makeText(context, "Permission Denied\n$deniedPermissions", Toast.LENGTH_SHORT) + .show() } - }; - return view; + } + return view } @OnClick(R.id.generate_button) - void generateKey() { - if (!Objects.requireNonNull(byteSize.getText()).toString().equalsIgnoreCase("") - && !Objects.requireNonNull(privateLocation.getText()).toString().equalsIgnoreCase("") - && !Objects.requireNonNull(publicLocation.getText()).toString().equalsIgnoreCase("")) { - if (Integer.valueOf(byteSize.getText().toString()) >= 1024) { - TedPermission.with(requireContext()) - .setPermissionListener(extract) - .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]") - .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE) - .check(); + fun generateKey() { + if (!Objects.requireNonNull(byteSize!!.text).toString() + .equals("", ignoreCase = true) && !Objects.requireNonNull( + privateLocation!!.text + ).toString().equals("", ignoreCase = true) && !Objects.requireNonNull( + publicLocation!!.text + ).toString().equals("", ignoreCase = true) + ) { + if (byteSize!!.text.toString().toInt() >= 1024) { + TedPermission.create() + .setPermissionListener(extract) + .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]") + .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE) + .check() } } } - private class GenerateKey extends AsyncTask { - - private WeakReference activityReference; - - // only retain a weak reference to the activity - GenerateKey(RSAGenerateKeyFragment context) { - activityReference = new WeakReference<>(context); + private inner class GenerateKey(context: RSAGenerateKeyFragment) : + AsyncTask() { + private val activityReference = + WeakReference(context) + + override fun doInBackground(vararg strings: String): Void? { + val byteSize = strings[0].toInt() + id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA.generateKey( + byteSize, strings[1], + strings[2] + ) + return null } - @Override - protected Void doInBackground(String... strings) { - int byteSize = Integer.valueOf(strings[0]); - RSA.generateKey(byteSize, strings[1], strings[2]); - return null; + override fun onPostExecute(aVoid: Void?) { + dialog!!.dismiss() + Toast.makeText(context, "Finished", Toast.LENGTH_SHORT).show() } + } - @Override - protected void onPostExecute(Void aVoid) { - - RSAGenerateKeyFragment activity = activityReference.get(); - if (activity == null) return; - activity.loadingView.dismiss(); - Toast.makeText(getContext(), "Finished", Toast.LENGTH_SHORT).show(); + companion object { + @JvmStatic + fun newInstance(): RSAGenerateKeyFragment { + return RSAGenerateKeyFragment() } } - } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt index e4709f1..4f0ac7c 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt @@ -1,187 +1,201 @@ -package id.my.berviantoleo.ecceg_rsa_app.lib.ecc; - -import java.math.BigInteger; - -public class ECC { - public BigInteger a, b, p; // y = x^3 + ax + b mod p - public BigInteger k = BIG_ZERO; - private static final BigInteger BIG_ZERO = BigInteger.ZERO, BIG_ONE = BigInteger.valueOf(1), - BIG_TWO = BigInteger.valueOf(2); - - public ECC() { - this.a = BIG_ZERO; - this.b = BIG_ZERO; - this.p = BIG_ZERO; // p is a prime +package id.my.berviantoleo.ecceg_rsa_app.lib.ecc + +import java.math.BigInteger + +class ECC { + @JvmField + var a: BigInteger + @JvmField + var b: BigInteger + @JvmField + var p: BigInteger // y = x^3 + ax + b mod p + @JvmField + var k: BigInteger = BIG_ZERO + + init { + this.a = BIG_ZERO + this.b = BIG_ZERO + this.p = BIG_ZERO // p is a prime } - private Point doubled(Point a) { - BigInteger tigaXkuadrat = BigInteger.valueOf(3).multiply(a.x).multiply(a.x); - BigInteger duaY = BigInteger.valueOf(2).multiply(a.y); - BigInteger inverseDuaY = duaY.modInverse(this.p); - BigInteger m = tigaXkuadrat.add(this.a).multiply(inverseDuaY).mod(this.p); - - BigInteger x = m.multiply(m).subtract(BigInteger.valueOf(2).multiply(a.x)).add(this.p).mod(this.p); - BigInteger y = m.multiply(a.x.subtract(x)).subtract(a.y).add(this.p).mod(this.p); - Point point = new Point(); - point.x = x; - point.y = y; - return point; + private fun doubled(a: Point): Point { + val tigaXkuadrat = BigInteger.valueOf(3).multiply(a.x).multiply(a.x) + val duaY = BigInteger.valueOf(2).multiply(a.y) + val inverseDuaY = duaY.modInverse(this.p) + val m = tigaXkuadrat.add(this.a).multiply(inverseDuaY).mod(this.p) + + val x = m.multiply(m).subtract(BigInteger.valueOf(2).multiply(a.x)).add(this.p).mod(this.p) + val y = m.multiply(a.x?.subtract(x)).subtract(a.y).add(this.p).mod(this.p) + val point = Point() + point.x = x + point.y = y + return point } - Point add(Point p, Point q) { + fun add(p: Point, q: Point): Point { if (p.infinity && q.infinity) { - Point point = new Point(); - point.infinity = true; - return point; - } else if (p.infinity) // identitas, mengembalikan q - return q; - else if (q.infinity) // identitas, mengembalikan p - return p; - else if (p.x.compareTo(q.x) == 0 && p.y.compareTo(q.y) == 0) // absis dan ordinat sama, titik yang sama - return doubled(p); + val point = Point() + point.infinity = true + return point + } else if (p.infinity) // identitas, mengembalikan q + return q + else if (q.infinity) // identitas, mengembalikan p + return p + else if (p.x.compareTo(q.x) == 0 && p.y.compareTo(q.y) == 0) // absis dan ordinat sama, titik yang sama + return doubled(p) + // else if (p.x.compareTo(q.x) == 0) // return new Point(true); - - BigInteger m = p.y.subtract(q.y).multiply(p.x.subtract(q.x).modInverse(this.p)).add(this.p).mod(this.p); - BigInteger x = m.multiply(m).subtract(p.x).subtract(q.x).add(this.p).mod(this.p); - BigInteger y = m.multiply(p.x.subtract(x)).subtract(p.y).add(this.p).mod(this.p); - Point point = new Point(); - point.x = x; - point.y = y; - return point; + val m = + p.y.subtract(q.y).multiply(p.x.subtract(q.x).modInverse(this.p)).add(this.p).mod(this.p) + val x = m.multiply(m).subtract(p.x).subtract(q.x).add(this.p).mod(this.p) + val y = m.multiply(p.x.subtract(x)).subtract(p.y).add(this.p).mod(this.p) + val point = Point() + point.x = x + point.y = y + return point } - private Point multiplyGenap(BigInteger n, Point p) { // rekursif, n diharapkan genap, n tidak 0 + private fun multiplyGenap( + n: BigInteger, + p: Point + ): Point { // rekursif, n diharapkan genap, n tidak 0 // System.out.println(n); - if (n.equals(BIG_ONE)) { // basis + if (n == BIG_ONE) { // basis // System.out.println("cek1"); - return p; - } else if (n.mod(BIG_TWO).equals(BIG_ZERO)) { // rekurens + return p + } else if (n.mod(BIG_TWO) == BIG_ZERO) { // rekurens // System.out.println("cek2"); - return multiplyGenap(n.divide(BIG_TWO), doubled(p)); - } else return multiplyGanjil(n, p); + return multiplyGenap(n.divide(BIG_TWO), doubled(p)) + } else return multiplyGanjil(n, p) } - private Point multiplyGanjil(BigInteger n, Point p) { // rekursif, n ganjil tidak 0 + private fun multiplyGanjil(n: BigInteger, p: Point): Point { // rekursif, n ganjil tidak 0 // System.out.println(n); - if (n.equals(BIG_ONE)) // basis - return p; + if (n == BIG_ONE) // basis + return p else { // System.out.println("cek3"); - return add(multiplyGenap(n.subtract(BigInteger.valueOf(1)), p), p); + return add(multiplyGenap(n.subtract(BigInteger.valueOf(1)), p), p) } } - Point multiply(BigInteger n, Point p) { - if (n.equals(BIG_ZERO)) - return new Point(); - else if (n.mod(BIG_TWO).equals(BIG_ZERO)) // rekurens - return multiplyGenap(n.divide(BIG_TWO), doubled(p)); - else return multiplyGanjil(n, p); + fun multiply(n: BigInteger, p: Point): Point { + if (n == BIG_ZERO) return Point() + else if (n.mod(BIG_TWO) == BIG_ZERO) // rekurens + return multiplyGenap(n.divide(BIG_TWO), doubled(p)) + else return multiplyGanjil(n, p) } - private BigInteger cariY(BigInteger x) { - BigInteger xPangkatTiga = x.multiply(x).multiply(x); - BigInteger axPlusb = this.a.multiply(x).add(b); - BigInteger y2 = xPangkatTiga.add(axPlusb).mod(p); - return sqrtP(y2, p); + private fun cariY(x: BigInteger): BigInteger? { + val xPangkatTiga = x.multiply(x).multiply(x) + val axPlusb = this.a.multiply(x).add(b) + val y2 = xPangkatTiga.add(axPlusb).mod(p) + return sqrtP(y2, p) } - Point intToPoint(BigInteger m) { - BigInteger mk = m.multiply(k); - for (BigInteger i = BIG_ONE; i.compareTo(k) < 0; i = i.add(BIG_ONE)) { - BigInteger x = mk.add(i); - BigInteger y = cariY(x); + fun intToPoint(m: BigInteger): Point { + val mk = m.multiply(k) + var i: BigInteger = BIG_ONE + while (i.compareTo(k) < 0) { + val x = mk.add(i) + val y = cariY(x) if (y != null) { - Point point = new Point(); - point.x = x.mod(p); - point.y = y.mod(p); - return point; + val point = Point() + point.x = x.mod(p) + point.y = y.mod(p) + return point } + i = i.add(BIG_ONE) } - Point point = new Point(); - point.x = point.y = BigInteger.valueOf(-1); - return point; + val point = Point() + point.y = BigInteger.valueOf(-1) + point.x = point.y + return point } - public BigInteger pointToInt(Point p) { - return p.x.subtract(BIG_ONE).divide(this.k); + fun pointToInt(p: Point): BigInteger { + return p.x.subtract(BIG_ONE).divide(this.k) } - public Point getBasePoint() { - for (BigInteger x = BIG_ZERO; x.compareTo(this.p) < 0; x = x.add(BIG_ONE)) { - BigInteger y = cariY(x); - if (y != null) { - Point point = new Point(); - point.x = x; - point.y = y; - return point; + val basePoint: Point? + get() { + var x: BigInteger = BIG_ZERO + while (x.compareTo(this.p) < 0) { + val y = cariY(x) + if (y != null) { + val point = + Point() + point.x = x + point.y = y + return point + } + x = x.add(BIG_ONE) } + return null } - return null; - } - private static BigInteger findNonResidue(BigInteger p) { - BigInteger a = BIG_TWO; - BigInteger q = p.subtract(BIG_ONE).divide(BIG_TWO); - while (true) { - if (a.modPow(q, p).equals(BIG_ONE)) - return a; + private fun sqrtP(x: BigInteger, p: BigInteger): BigInteger? { + if (p.mod(BIG_TWO) == BIG_ZERO) return null + var q = p.subtract(BIG_ONE).divide(BIG_TWO) + if (x.modPow(q, p) != BIG_ONE) return null - a = a.add(BIG_ONE); - if (a.compareTo(p) >= 0) - return null; + while (q.mod(BIG_TWO) == BIG_ZERO) { + q = q.divide(BIG_TWO) + if (x.modPow(q, p) != BIG_ONE) return complexSqrtP(x, q, p) } + q = q.add(BIG_ONE).divide(BIG_TWO) + return x.modPow(q, p) } - private static BigInteger complexSqrtP(BigInteger x, BigInteger q, BigInteger p) { - BigInteger a = findNonResidue(p); - if (a == null) - return null; - BigInteger t = p.subtract(BIG_ONE).divide(BIG_TWO); - BigInteger minusPower = t; - - while (q.mod(BIG_TWO).equals(BIG_ZERO)) { - q = q.divide(BIG_TWO); - t = t.divide(BIG_TWO); - if (x.modPow(q, p).compareTo(a.modPow(t, p)) != 0) - t = t.add(minusPower); - } - BigInteger inverseX = x.modInverse(p); - BigInteger partOne = inverseX.modPow(q.subtract(BIG_ONE).divide(BIG_TWO), p); - BigInteger partTwo = a.modPow(t.divide(BIG_TWO), p); - return partOne.multiply(partTwo).mod(p); - } - private BigInteger sqrtP(BigInteger x, BigInteger p) { - if (p.mod(BIG_TWO).equals(BIG_ZERO)) - return null; - BigInteger q = p.subtract(BIG_ONE).divide(BIG_TWO); - if (!x.modPow(q, p).equals(BIG_ONE)) - return null; - - while (q.mod(BIG_TWO).equals(BIG_ZERO)) { - q = q.divide(BIG_TWO); - if (!x.modPow(q, p).equals(BIG_ONE)) - return complexSqrtP(x, q, p); + companion object { + private val BIG_ZERO: BigInteger = BigInteger.ZERO + private val BIG_ONE: BigInteger = BigInteger.valueOf(1) + private val BIG_TWO: BigInteger = BigInteger.valueOf(2) + + private fun findNonResidue(p: BigInteger): BigInteger? { + var a: BigInteger? = BIG_TWO + val q = p.subtract(BIG_ONE).divide(BIG_TWO) + while (true) { + if (a!!.modPow(q, p) == BIG_ONE) return a + + a = a.add(BIG_ONE) + if (a.compareTo(p) >= 0) return null + } } - q = q.add(BIG_ONE).divide(BIG_TWO); - return x.modPow(q, p); - } + private fun complexSqrtP(x: BigInteger, q: BigInteger, p: BigInteger): BigInteger? { + var q = q + val a: BigInteger? = findNonResidue(p) + if (a == null) return null + var t = p.subtract(BIG_ONE).divide(BIG_TWO) + val minusPower = t + + while (q.mod(BIG_TWO) == BIG_ZERO) { + q = q.divide(BIG_TWO) + t = t.divide(BIG_TWO) + if (x.modPow(q, p).compareTo(a.modPow(t, p)) != 0) t = t.add(minusPower) + } + val inverseX = x.modInverse(p) + val partOne = inverseX.modPow(q.subtract(BIG_ONE).divide(BIG_TWO), p) + val partTwo = a.modPow(t.divide(BIG_TWO), p) + return partOne.multiply(partTwo).mod(p) + } - public static void main(String[] args) { - ECC ecc = new ECC(); - ecc.a = BigInteger.valueOf(2); - ecc.b = BigInteger.valueOf(6); - ecc.p = BigInteger.valueOf(15485867); - Point point1 = new Point(); - point1.x = BigInteger.valueOf(2); - point1.y = BigInteger.valueOf(6); - System.out.println(point1.toString()); - BigInteger n = BigInteger.valueOf(5); - point1 = ecc.multiply(n, point1); - System.out.println(point1.toString()); + @JvmStatic + fun main(args: Array) { + val ecc = ECC() + ecc.a = BigInteger.valueOf(2) + ecc.b = BigInteger.valueOf(6) + ecc.p = BigInteger.valueOf(15485867) + var point1 = Point() + point1.x = BigInteger.valueOf(2) + point1.y = BigInteger.valueOf(6) + println(point1.toString()) + val n = BigInteger.valueOf(5) + point1 = ecc.multiply(n, point1) + println(point1.toString()) + } } - } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt index 95e0f16..01bcfc5 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt @@ -1,119 +1,122 @@ -package id.my.berviantoleo.ecceg_rsa_app.lib.ecc; +package id.my.berviantoleo.ecceg_rsa_app.lib.ecc -import java.io.File; -import java.io.FileOutputStream; -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.Scanner; +import java.io.File +import java.io.FileOutputStream +import java.math.BigInteger +import java.security.SecureRandom +import java.util.Random +import java.util.Scanner -public class ECCEG { - private Point publicKey; - private BigInteger privateKey; - private Point basePoint; - private ECC ECC; +class ECCEG { + @JvmField + var publicKey: Point + private var privateKey: BigInteger? + val basePoint: Point + val eCC: ECC - public ECCEG(ECC ECC, Point basePoint) { - this.ECC = ECC; - this.basePoint = basePoint; - this.privateKey = new BigInteger(ECC.p.bitLength(), new Random()) + constructor(ECC: ECC, basePoint: Point) { + this.eCC = ECC + this.basePoint = basePoint + this.privateKey = BigInteger(ECC.p.bitLength(), Random()) .mod(ECC.p.subtract(BigInteger.ONE)) - .add(BigInteger.ONE); - this.publicKey = ECC.multiply(privateKey, basePoint); + .add(BigInteger.ONE) + this.publicKey = ECC.multiply(privateKey!!, basePoint) } - public ECCEG(ECC ECC, Point basePoint, BigInteger privateKey) { - this.ECC = ECC; - this.basePoint = basePoint; - this.privateKey = privateKey; - this.publicKey = ECC.multiply(privateKey, basePoint); + constructor(ECC: ECC, basePoint: Point, privateKey: BigInteger) { + this.eCC = ECC + this.basePoint = basePoint + this.privateKey = privateKey + this.publicKey = ECC.multiply(privateKey, basePoint) } - public Point getPublicKey() { return this.publicKey; } - public BigInteger getPrivateKey() { return this.privateKey; } - public ECC getECC() { return this.ECC; } - public Point getBasePoint() { return this.basePoint; } + fun getPrivateKey(): BigInteger { + return this.privateKey!! + } - public void setPublicKey(Point publicKey) { this.publicKey = publicKey; } - public void setPrivateKey(BigInteger privateKey) { this.privateKey = privateKey; } + fun setPrivateKey(privateKey: BigInteger) { + this.privateKey = privateKey + } - public void savePublicKey(String fileName) throws Exception { + @Throws(Exception::class) + fun savePublicKey(fileName: String) { // disimpan x dan y, dipisahkan dengan spasi - File file = new File(fileName); - if (!file.exists()) file.createNewFile(); - FileOutputStream out = new FileOutputStream(file); - out.write(publicKey.x.toString().getBytes()); - out.write(' '); - out.write(publicKey.y.toString().getBytes()); - out.flush(); - out.close(); + val file = File(fileName) + if (!file.exists()) file.createNewFile() + val out = FileOutputStream(file) + out.write(publicKey.x.toString().toByteArray()) + out.write(' '.code) + out.write(publicKey.y.toString().toByteArray()) + out.flush() + out.close() } - public void savePrivateKey(String fileName) throws Exception { - File file = new File(fileName); - if (!file.exists()) file.createNewFile(); - FileOutputStream out = new FileOutputStream(file); - out.write(privateKey.toString().getBytes()); - out.flush(); - out.close(); + @Throws(Exception::class) + fun savePrivateKey(fileName: String) { + val file = File(fileName) + if (!file.exists()) file.createNewFile() + val out = FileOutputStream(file) + out.write(privateKey.toString().toByteArray()) + out.flush() + out.close() } - public void loadPublicKey(String fileName) throws Exception { - File file = new File(fileName); - Scanner sc = new Scanner(file); - BigInteger x = null, y = null; - if (sc.hasNextBigInteger()) x = sc.nextBigInteger(); - if (sc.hasNextBigInteger()) y = sc.nextBigInteger(); - sc.close(); + @Throws(Exception::class) + fun loadPublicKey(fileName: String) { + val file = File(fileName) + val sc = Scanner(file) + var x: BigInteger? = null + var y: BigInteger? = null + if (sc.hasNextBigInteger()) x = sc.nextBigInteger() + if (sc.hasNextBigInteger()) y = sc.nextBigInteger() + sc.close() if (x != null && y != null) { - Point point = new Point(); - point.x = x; - point.y = y; - this.publicKey = point; + val point = Point() + point.x = x + point.y = y + this.publicKey = point } } - public void loadPrivateKey(String fileName) throws Exception { - File file = new File(fileName); - Scanner sc = new Scanner(file); - BigInteger i = null; - if (sc.hasNextBigInteger()) i = sc.nextBigInteger(); - sc.close(); + @Throws(Exception::class) + fun loadPrivateKey(fileName: String) { + val file = File(fileName) + val sc = Scanner(file) + var i: BigInteger? = null + if (sc.hasNextBigInteger()) i = sc.nextBigInteger() + sc.close() if (i != null) { - this.privateKey = i; + this.privateKey = i } } - public Pair encrypt(Point p) { - BigInteger k = new BigInteger(ECC.p.bitLength(), new SecureRandom()) - .mod(ECC.p.subtract(BigInteger.ONE)) - .add(BigInteger.ONE); - Point left = ECC.multiply(k, basePoint); - Point right = ECC.add(p, ECC.multiply(k, publicKey)); - return new Pair(left, right); + fun encrypt(p: Point): Pair { + val k = BigInteger(eCC.p.bitLength(), SecureRandom()) + .mod(eCC.p.subtract(BigInteger.ONE)) + .add(BigInteger.ONE) + val left: Point = eCC.multiply(k, basePoint) + val right: Point = eCC.add(p, eCC.multiply(k, publicKey)) + return Pair(left, right) } - public List> encryptBytes(byte[] bytes) { - List> ret = new ArrayList<>(); - for (byte aByte : bytes) ret.add(encrypt(ECC.intToPoint(BigInteger.valueOf(aByte)))); - return ret; + fun encryptBytes(bytes: ByteArray): MutableList?> { + val ret: MutableList?> = ArrayList?>() + for (aByte in bytes) ret.add(encrypt(eCC.intToPoint(BigInteger.valueOf(aByte.toLong())))) + return ret } - public Point decrypt(Pair p) { - Point m = ECC.multiply(privateKey, p.left); + fun decrypt(p: Pair): Point { + val m: Point = eCC.multiply(privateKey!!, p.left!!) - Point minusM = new Point(); - minusM.x = m.x; - minusM.y = m.y.negate().mod(ECC.p); - return ECC.add(p.right, minusM); + val minusM = Point() + minusM.x = m.x + minusM.y = m.y?.negate()?.mod(eCC.p) + return eCC.add(p.right!!, minusM) } - public List decrypt(List> l) { - List ret = new ArrayList<>(); - for (Pair p: l) - ret.add(decrypt(p)); - return ret; + fun decrypt(l: MutableList>): MutableList { + val ret: MutableList = ArrayList() + for (p in l) ret.add(decrypt(p)) + return ret } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt index b56dd9a..4756969 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt @@ -1,99 +1,114 @@ -package id.my.berviantoleo.ecceg_rsa_app.lib.ecc; +package id.my.berviantoleo.ecceg_rsa_app.lib.ecc -import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils; +import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils.getBytes +import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils.loadPointsFromFile +import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils.savePointsToFile +import java.math.BigInteger +import java.util.Scanner -import java.math.BigInteger; -import java.util.List; -import java.util.Scanner; - -public class ECCEGMain { - public static void main(String[] args) throws Exception { // unhandled exception +object ECCEGMain { + @Throws(Exception::class) + @JvmStatic + fun main(args: Array) { // unhandled exception // Initiate Classes Component - BigInteger a = new BigInteger("2"), b = new BigInteger("6"), p = new BigInteger("15485867"), - k = new BigInteger("30"); + + val a = BigInteger("2") + val b = BigInteger("6") + val p = BigInteger("15485867") + val k = BigInteger("30") // y = x^3 + 2x + 6 mod 15485867 // this is programmer defined, but needs to be consistent for encrypt and decrypt - ECC ecc = new ECC(); - ecc.a = a; - ecc.b = b; - ecc.p = p; - ecc.k = k; + val ecc = ECC() + ecc.a = a + ecc.b = b + ecc.p = p + ecc.k = k // Initiate ended - - System.out.println("Choose an action : "); - System.out.println("1. Generate Key"); - System.out.println("2. Encrypt File"); - System.out.println("3. Decrypt File"); - System.out.print("Input 1/2/3 = "); - Scanner s = new Scanner(System.in); - int chosen = s.nextInt(); + println("Choose an action : ") + println("1. Generate Key") + println("2. Encrypt File") + println("3. Decrypt File") + print("Input 1/2/3 = ") + val s = Scanner(System.`in`) + val chosen = s.nextInt() if (chosen == 1) { - String private_key_filepath = "key.pri"; - String public_key_filepath = "key.pub"; - ECCEGMain.generateKey(ecc, private_key_filepath, public_key_filepath); + val private_key_filepath = "key.pri" + val public_key_filepath = "key.pub" + generateKey(ecc, private_key_filepath, public_key_filepath) } else if (chosen == 2) { - String public_key_filepath = "key.pub"; - String plainfile = "datatest.txt"; - String ciphfile = plainfile + ".ciph"; - ECCEGMain.encryptFile(ecc, public_key_filepath, plainfile, ciphfile); + val public_key_filepath = "key.pub" + val plainfile = "datatest.txt" + val ciphfile = plainfile + ".ciph" + encryptFile(ecc, public_key_filepath, plainfile, ciphfile) } else if (chosen == 3) { - String private_key_filepath = "key.pri"; - String plainfile = "datatest.txt"; - String ciphfile = "datatest.txt.ciph"; - ECCEGMain.decryptFile(ecc, private_key_filepath, ciphfile, plainfile); - } else System.out.println("Wrong choice!!"); + val private_key_filepath = "key.pri" + val plainfile = "datatest.txt" + val ciphfile = "datatest.txt.ciph" + decryptFile(ecc, private_key_filepath, ciphfile, plainfile) + } else println("Wrong choice!!") } - public static void generateKey(ECC ecc, String private_key_filepath, String public_key_filepath) throws Exception { - ECCEG ecceg = new ECCEG(ecc, ecc.getBasePoint()); - System.out.print("Private key = "); - System.out.println(ecceg.getPrivateKey()); - ecceg.savePrivateKey(private_key_filepath); - System.out.println("Saved in " + private_key_filepath); - System.out.print("Public key = "); - System.out.println(ecceg.getPublicKey()); - ecceg.savePublicKey(public_key_filepath); - System.out.println("Saved in " + public_key_filepath); + @JvmStatic + @Throws(Exception::class) + fun generateKey(ecc: ECC, private_key_filepath: String, public_key_filepath: String) { + val ecceg = ECCEG(ecc, ecc.basePoint!!) + print("Private key = ") + println(ecceg.getPrivateKey()) + ecceg.savePrivateKey(private_key_filepath) + println("Saved in " + private_key_filepath) + print("Public key = ") + println(ecceg.publicKey) + ecceg.savePublicKey(public_key_filepath) + println("Saved in " + public_key_filepath) } - private static void encryptFile(ECC ecc, String public_key_filepath, String filepath_plain, - String filepath_ciph) throws Exception { - ECCEG ecceg = new ECCEG(ecc, ecc.getBasePoint()); - ecceg.loadPublicKey(public_key_filepath); - System.out.println("Public key loaded..."); - byte[] read = FileUtils.getBytes(filepath_plain); - System.out.println("---===Plainteks===---"); - System.out.println(new String(read)); - System.out.println("---======END======---"); - System.out.println(); - List> enc = ecceg.encryptBytes(read); - System.out.println("---===Cipherteks===---"); - for (Pair pp : enc) { - System.out.print(String.format("%02x%02x%02x%02x", - pp.left.x.intValue(), - pp.left.y.intValue(), - pp.right.x.intValue(), - pp.right.y.intValue())); + @Throws(Exception::class) + private fun encryptFile( + ecc: ECC, public_key_filepath: String, filepath_plain: String, + filepath_ciph: String? + ) { + val ecceg = ECCEG(ecc, ecc.basePoint!!) + ecceg.loadPublicKey(public_key_filepath) + println("Public key loaded...") + val read = getBytes(filepath_plain) + println("---===Plainteks===---") + println(kotlin.text.String(read!!)) + println("---======END======---") + println() + val enc: MutableList> = ecceg.encryptBytes(read) + println("---===Cipherteks===---") + for (pp in enc) { + print( + String.format( + "%02x%02x%02x%02x", + pp.left!!.x.toInt(), + pp.left!!.y.toInt(), + pp.right!!.x.toInt(), + pp.right!!.y.toInt() + ) + ) } - System.out.println(); - System.out.println("---======END======---"); - FileUtils.savePointsToFile(filepath_ciph, enc); + println() + println("---======END======---") + savePointsToFile(filepath_ciph, enc) } - private static void decryptFile(ECC ecc, String private_key_filepath, - String filepath_ciph, String destination) throws Exception { - ECCEG ecceg = new ECCEG(ecc, ecc.getBasePoint()); - ecceg.loadPrivateKey(private_key_filepath); - System.out.println("Private key loaded..."); - List> read_enc = FileUtils.loadPointsFromFile(filepath_ciph); - List read_dec = ecceg.decrypt(read_enc); - System.out.println("---===Plainteks===---"); - for (Point pp : read_dec) - System.out.print((char) ecc.pointToInt(pp).byteValue()); - System.out.println(); - System.out.println("---======END======---"); + @Throws(Exception::class) + private fun decryptFile( + ecc: ECC, private_key_filepath: String, + filepath_ciph: String, destination: String? + ) { + val ecceg = ECCEG(ecc, ecc.basePoint!!) + ecceg.loadPrivateKey(private_key_filepath) + println("Private key loaded...") + val read_enc = loadPointsFromFile(filepath_ciph) + val read_dec: MutableList = ecceg.decrypt(read_enc) + println("---===Plainteks===---") + for (pp in read_dec) print(Char(ecc.pointToInt(pp).toByte().toUShort())) + println() + println("---======END======---") } } \ No newline at end of file diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.kt index 70441f4..ad4b06c 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Pair.kt @@ -1,31 +1,17 @@ -package id.my.berviantoleo.ecceg_rsa_app.lib.ecc; +package id.my.berviantoleo.ecceg_rsa_app.lib.ecc -import androidx.annotation.NonNull; - -public class Pair { - public L left; - public R right; - - public Pair(L left, R right) { - this.left = left; - this.right = right; - } - - @Override - public int hashCode() { - return left.hashCode() ^ right.hashCode(); +class Pair(var left: L?, var right: R?) { + override fun hashCode(): Int { + return left.hashCode() xor right.hashCode() } - @Override - public boolean equals(Object o) { - if (!(o instanceof Pair)) return false; - Pair pair = (Pair) o; - return left.equals(pair.left) && right.equals(pair.right); + override fun equals(other: Any?): Boolean { + if (other !is Pair<*, *>) return false + val pair = other + return left == pair.left && right == pair.right } - @NonNull - @Override - public String toString() { - return "<" + left.toString() + ", " + right.toString() + ">"; + override fun toString(): String { + return "<$left, $right>" } } \ No newline at end of file diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt index 16acde8..1c004ca 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt @@ -1,21 +1,13 @@ -package id.my.berviantoleo.ecceg_rsa_app.lib.ecc; +package id.my.berviantoleo.ecceg_rsa_app.lib.ecc -import java.math.BigInteger; +import java.math.BigInteger -import androidx.annotation.NonNull; +class Point { + var x: BigInteger? = BigInteger.ZERO + var y: BigInteger? = BigInteger.ZERO // x = absis, y = ordinat + var infinity: Boolean = false // titik O, elemen identitas -public class Point { - public BigInteger x, y; // x = absis, y = ordinat - boolean infinity; // titik O, elemen identitas - - public Point() { - x = BigInteger.ZERO; - y = BigInteger.ZERO; - infinity = false; - } - - @NonNull - public String toString() { - return "(" + x + ", " + y + ")"; + override fun toString(): String { + return "($x, $y)" } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt index 5c0637f..f2a55c6 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt @@ -1,313 +1,337 @@ -package id.my.berviantoleo.ecceg_rsa_app.lib.rsa; - -import android.util.Log; - -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Random; - -public class RSA { - +package id.my.berviantoleo.ecceg_rsa_app.lib.rsa + +import android.util.Log +import java.io.BufferedOutputStream +import java.io.BufferedReader +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.FileReader +import java.io.IOException +import java.io.OutputStreamWriter +import java.math.BigInteger +import java.security.SecureRandom +import java.util.Random +import kotlin.math.ceil + +object RSA { /** * Returns true when the argument is null. */ - private static boolean isNull(Object obj) { - return obj == null; + private fun isNull(obj: Any?): Boolean { + return obj == null } - public static void generateKey(int bitLength, String privateName, String publicName) { - SecureRandom rnd = new SecureRandom(); - BigInteger p = BigInteger.probablePrime(75 * bitLength / 100, rnd); - BigInteger q = BigInteger.probablePrime(25 * bitLength / 100, rnd); - BigInteger n = p.multiply(q); - BigInteger phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE)); - BigInteger i; - BigInteger pubExp = BigInteger.ONE; - for (i = BigInteger.probablePrime(bitLength / 10, rnd); i.compareTo(n) < 0; i = i.nextProbablePrime()) { - if (i.gcd(phi).equals(BigInteger.ONE)) { - pubExp = i; - break; + fun generateKey(bitLength: Int, privateName: String?, publicName: String?) { + val rnd = SecureRandom() + val p = BigInteger.probablePrime(75 * bitLength / 100, rnd) + val q = BigInteger.probablePrime(25 * bitLength / 100, rnd) + val n = p.multiply(q) + val phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE)) + var i: BigInteger + var pubExp = BigInteger.ONE + i = BigInteger.probablePrime(bitLength / 10, rnd) + while (i.compareTo(n) < 0) { + if (i.gcd(phi) == BigInteger.ONE) { + pubExp = i + break } + i = i.nextProbablePrime() } - BigInteger priExp = pubExp.modInverse(phi); - writeKeyToFile(privateName, n, priExp); - writeKeyToFile(publicName, n, pubExp); + val priExp = pubExp.modInverse(phi) + writeKeyToFile(privateName, n, priExp) + writeKeyToFile(publicName, n, pubExp) } - private static final String HEXES = "0123456789ABCDEF"; + private const val HEXES = "0123456789ABCDEF" - public static String showHexFromFile(String file) { - byte[] sourceBytes = getBytes(file); + @JvmStatic + fun showHexFromFile(file: String): String { + val sourceBytes = getBytes(file) if (isNull(sourceBytes)) { - return ""; + return "" } - return getHex(sourceBytes); + return RSA.getHex(sourceBytes!!) } - private static String getHex(byte[] raw) { - final StringBuilder hex = new StringBuilder(2 * raw.length); - for (final byte b : raw) { - hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); + private fun getHex(raw: ByteArray): String { + val hex = StringBuilder(2 * raw.size) + for (b in raw) { + hex.append(HEXES.get((b.toInt() and 0xF0) shr 4)) + .append(HEXES.get((b.toInt() and 0x0F))) } - return hex.toString(); + return hex.toString() } - private static void writeKeyToFile(String name, BigInteger n, BigInteger d) { - String output = n.toString() + ":" + d.toString(); + private fun writeKeyToFile(name: String?, n: BigInteger, d: BigInteger) { + val output = n.toString() + ":" + d.toString() try { - FileOutputStream writer = new FileOutputStream(name); - OutputStreamWriter outWriter = new OutputStreamWriter(writer); - outWriter.write(output); - outWriter.close(); - writer.flush(); - writer.close(); - } catch (IOException e) { - Log.e("RSA", e.getMessage()); + val writer = FileOutputStream(name) + val outWriter = OutputStreamWriter(writer) + outWriter.write(output) + outWriter.close() + writer.flush() + writer.close() + } catch (e: IOException) { + Log.e("RSA", e.message!!) } } - public static String readKey(String location) { - String value = ":"; - try (BufferedReader br = new BufferedReader(new FileReader(location))) { - String sCurrentLine = br.readLine(); - if (sCurrentLine != null) { - value = sCurrentLine; + @JvmStatic + fun readKey(location: String?): String { + var value: String? = ":" + try { + BufferedReader(FileReader(location)).use { br -> + val sCurrentLine = br.readLine() + if (sCurrentLine != null) { + value = sCurrentLine + } } - } catch (IOException e) { - e.printStackTrace(); - return value; + } catch (e: IOException) { + e.printStackTrace() + return value!! } - return value; + return value!! } - public static boolean decryptFile(String source, String destination, BigInteger d, BigInteger n) { - byte[] sourceBytes = getBytes(source); + @JvmStatic + fun decryptFile(source: String, destination: String?, d: BigInteger, n: BigInteger): Boolean { + val sourceBytes = getBytes(source) if (isNull(sourceBytes)) { - return false; + return false } - int k = (int) Math.ceil(n.bitLength() / 8.0); - BigInteger c, m; - byte[] EB, M; - byte[][] C = reshape(sourceBytes, k); - BufferedOutputStream out; + val k = ceil(n.bitLength() / 8.0).toInt() + var c: BigInteger? + var m: BigInteger? + var EB: ByteArray? + var M: ByteArray? + val C = RSA.reshape(sourceBytes!!, k) + val out: BufferedOutputStream? try { if (C != null) { - out = new BufferedOutputStream(new FileOutputStream(destination)); - for (byte[] aC : C) { - if (aC.length != k) - return false; - c = new BigInteger(aC); - m = decrypt(c, d, n); - EB = toByteArray(m, k); + out = BufferedOutputStream(FileOutputStream(destination)) + for (aC in C) { + if (aC.size != k) return false + c = BigInteger(aC) + m = decrypt(c, d, n) + EB = toByteArray(m, k) if (EB != null) { - M = extractData(EB); - out.write(M); + M = extractData(EB) + out.write(M) } } - out.close(); + out.close() } else { - return false; + return false } - out.close(); - } catch (IOException e) { - return false; + out.close() + } catch (e: IOException) { + return false } - return true; + return true } /** * Extracts the data portion of the byte array. */ - private static byte[] extractData(byte[] EB) { - if (EB.length < 12 || EB[0] != 0x00 || EB[1] != 0x02) { - return null; + private fun extractData(EB: ByteArray): ByteArray? { + if (EB.size < 12 || EB[0].toInt() != 0x00 || EB[1].toInt() != 0x02) { + return null } - int index = 2; + var index = 2 do { + } while (EB[index++].toInt() != 0x00) - } while (EB[index++] != 0x00); - - return getSubArray(EB, index, EB.length); + return getSubArray(EB, index, EB.size) } /** * Performs the classical RSA computation. */ - private static BigInteger decrypt(BigInteger c, BigInteger d, BigInteger n) { - return c.modPow(d, n); + private fun decrypt(c: BigInteger, d: BigInteger, n: BigInteger): BigInteger { + return c.modPow(d, n) } - public static byte[] getBytes(String fileName) { - File fIn = new File(fileName); + @JvmStatic + fun getBytes(fileName: String): ByteArray? { + val fIn = File(fileName) if (!fIn.canRead()) { - System.err.println("Can't read " + fileName); - return null; + System.err.println("Can't read " + fileName) + return null } - byte[] bytes = null; - try (FileInputStream in = new FileInputStream(fIn)) { - - long fileSize = fIn.length(); - if (fileSize > Integer.MAX_VALUE) { - System.out.println("Sorry, file was too large!"); - } + var bytes: ByteArray? = null + try { + FileInputStream(fIn).use { `in` -> + val fileSize = fIn.length() + if (fileSize > Int.Companion.MAX_VALUE) { + println("Sorry, file was too large!") + } - bytes = new byte[(int) fileSize]; + bytes = ByteArray(fileSize.toInt()) - int offset = 0; - int numRead; - while (offset < bytes.length && (numRead = in.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += numRead; + var offset = 0 + var numRead = 0 + while (offset < bytes.size && (`in`.read(bytes, offset, bytes.size - offset) + .also { numRead = it }) >= 0 + ) { + offset += numRead + } } - } catch (IOException ignored) { + } catch (ignored: IOException) { } - return bytes; + return bytes } /** * Performs the classical RSA computation. */ - private static BigInteger encrypt(BigInteger m, BigInteger e, BigInteger n) { - return m.modPow(e, n); + private fun encrypt(m: BigInteger, e: BigInteger, n: BigInteger): BigInteger { + return m.modPow(e, n) } /** * Uses the key and returns true if encryption was successful. */ - public static boolean encryptedFile(String source, String destination, BigInteger e, BigInteger n) { - byte[] sourceBytes = getBytes(source); + @JvmStatic + fun encryptedFile(source: String, destination: String?, e: BigInteger, n: BigInteger): Boolean { + val sourceBytes = getBytes(source) if (isNull(sourceBytes)) { - System.err.println(String.format("%s contained nothing.", source)); - return false; + System.err.println(String.format("%s contained nothing.", source)) + return false } - int k = (int) Math.ceil(n.bitLength() / 8.0); - byte BT = 0x02; - byte[] C, M; - byte[][] D = reshape(sourceBytes, k - 11); - ByteArrayOutputStream EB = new ByteArrayOutputStream(k); - FileOutputStream out; - BigInteger m, c; + val k = ceil(n.bitLength() / 8.0).toInt() + val BT: Byte = 0x02 + var C: ByteArray? + var M: ByteArray? + val D = RSA.reshape(sourceBytes!!, k - 11) + val EB = ByteArrayOutputStream(k) + val out: FileOutputStream? + var m: BigInteger? + var c: BigInteger? try { - if (D != null) { - out = new FileOutputStream(destination); - for (byte[] aD : D) { - EB.reset(); - EB.write(0x00); - EB.write(BT); - EB.write(makePaddingString(k - aD.length - 3)); - EB.write(0x00); - EB.write(aD); - M = EB.toByteArray(); - m = new BigInteger(M); - c = encrypt(m, e, n); - C = toByteArray(c, k); - out.write(C); + out = FileOutputStream(destination) + for (aD in D) { + EB.reset() + EB.write(0x00) + EB.write(BT.toInt()) + EB.write(makePaddingString(k - aD.size - 3)) + EB.write(0x00) + EB.write(aD) + M = EB.toByteArray() + m = BigInteger(M) + c = encrypt(m, e, n) + C = toByteArray(c, k) + out.write(C) } - out.close(); + out.close() } else { - return false; + return false } - } catch (Exception ex) { - String errMsg = "An exception occured!%n%s%n%s%n%s"; - System.err.println(String.format(errMsg, ex.getClass(), ex.getMessage(), Arrays.toString(ex.getStackTrace()))); - return false; + } catch (ex: Exception) { + val errMsg = "An exception occured!%n%s%n%s%n%s" + System.err.println( + String.format( + errMsg, + ex.javaClass, + ex.message, + ex.getStackTrace().contentToString() + ) + ) + return false } - return true; + return true } - private static byte[][] reshape(byte[] inBytes, int colSize) { + private fun reshape(inBytes: ByteArray, colSize: Int): Array? { + var colSize = colSize if (colSize < 1) { - colSize = 1; + colSize = 1 } - int rowSize = (int) Math.ceil((double) inBytes.length / (double) colSize); + val rowSize = ceil(inBytes.size.toDouble() / colSize.toDouble()).toInt() if (rowSize == 0) { - return null; + return null } - byte[][] outBytes = new byte[rowSize][]; + val outBytes: Array = arrayOfNulls(rowSize) - for (int i = 0; i < rowSize; i++) { - outBytes[i] = getSubArray(inBytes, i * colSize, (i + 1) * colSize); + for (i in 0..= inBytes.length) { - return null; + private fun getSubArray(inBytes: ByteArray, start: Int, end: Int): ByteArray? { + var end = end + if (start >= inBytes.size) { + return null } - if (end > inBytes.length) { - end = inBytes.length; + if (end > inBytes.size) { + end = inBytes.size } - int bytesToGet = end - start; + val bytesToGet = end - start if (bytesToGet < 1) { - return null; + return null } - byte[] outBytes = new byte[bytesToGet]; - if (end - start >= 0) - System.arraycopy(inBytes, start, outBytes, 0, end - start); + val outBytes = ByteArray(bytesToGet) + if (end - start >= 0) System.arraycopy(inBytes, start, outBytes, 0, end - start) - return outBytes; + return outBytes } /** * Converts a BigInteger into a byte array of the specified length. */ - private static byte[] toByteArray(BigInteger x, int numBytes) { + private fun toByteArray(x: BigInteger, numBytes: Int): ByteArray? { + var x = x + var numBytes = numBytes if (x.compareTo(BigInteger.valueOf(256).pow(numBytes)) >= 0) { - return null; // number is to big to fit in the byte array + return null // number is to big to fit in the byte array } - byte[] ba = new byte[numBytes--]; - BigInteger[] divAndRem; + val ba = ByteArray(numBytes--) + var divAndRem: Array? - for (int power = numBytes; power >= 0; power--) { - divAndRem = x.divideAndRemainder(BigInteger.valueOf(256).pow(power)); - ba[numBytes - power] = (byte) divAndRem[0].intValue(); - x = divAndRem[1]; + for (power in numBytes downTo 0) { + divAndRem = x.divideAndRemainder(BigInteger.valueOf(256).pow(power)) + ba[numBytes - power] = divAndRem[0]!!.toInt().toByte() + x = divAndRem[1]!! } - return ba; + return ba } /** * Generates an array of pseudo-random nonzero bytes. */ - private static byte[] makePaddingString(int len) { - if (len < 8) - return null; - Random random = new Random(); - - byte[] PS = new byte[len]; - for (int i = 0; i < len; i++) { - PS[i] = (byte) (random.nextInt(255) + 1); + private fun makePaddingString(len: Int): ByteArray? { + if (len < 8) return null + val random = Random() + + val PS = ByteArray(len) + for (i in 0.. Integer.MAX_VALUE) { - System.out.println("Sorry, file was too large!"); - } + var bytes: ByteArray? = null + try { + FileInputStream(fIn).use { `in` -> + val fileSize = fIn.length() + if (fileSize > Int.Companion.MAX_VALUE) { + println("Sorry, file was too large!") + } - bytes = new byte[(int) fileSize]; + bytes = ByteArray(fileSize.toInt()) - int offset = 0; - int numRead; - while (offset < bytes.length && (numRead = in.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += numRead; + var offset = 0 + var numRead = 0 + while (offset < bytes.size && (`in`.read(bytes, offset, bytes.size - offset) + .also { numRead = it }) >= 0 + ) { + offset += numRead + } } - } catch (IOException ignored) { + } catch (ignored: IOException) { } - return bytes; + return bytes } - public static void saveFile(String stringpath, byte[] content) { + @JvmStatic + fun saveFile(stringpath: String?, content: ByteArray?) { try { - FileOutputStream fos = new FileOutputStream(stringpath); - fos.write(content); - fos.close(); - } catch (IOException ignored) { + val fos = FileOutputStream(stringpath) + fos.write(content) + fos.close() + } catch (ignored: IOException) { } } - private static byte[] intToBytes(int x) { - return ByteBuffer.allocate(4).putInt(x).array(); + private fun intToBytes(x: Int): ByteArray { + return ByteBuffer.allocate(4).putInt(x).array() } - private static int bytesToInt(byte[] bytes) { - return ByteBuffer.wrap(bytes).getInt(); + private fun bytesToInt(bytes: ByteArray): Int { + return ByteBuffer.wrap(bytes).getInt() } - public static void savePointsToFile(String path, List> pairpoints) { - byte[] b = new byte[pairpoints.size() * 16]; - int j = 0; - for (Pair ppoint : pairpoints) { - byte[] btemp = intToBytes(ppoint.left.x.intValue()); - for (byte aBtemp : btemp) { - b[j] = aBtemp; - j++; + fun savePointsToFile(path: String?, pairpoints: MutableList>) { + val b = ByteArray(pairpoints.size * 16) + var j = 0 + for (ppoint in pairpoints) { + var btemp = intToBytes(ppoint.left!!.x.toInt()) + for (aBtemp in btemp) { + b[j] = aBtemp + j++ } - btemp = intToBytes(ppoint.left.y.intValue()); - for (byte aBtemp : btemp) { - b[j] = aBtemp; - j++; + btemp = intToBytes(ppoint.left!!.y.toInt()) + for (aBtemp in btemp) { + b[j] = aBtemp + j++ } - btemp = intToBytes(ppoint.right.x.intValue()); - for (byte aBtemp : btemp) { - b[j] = aBtemp; - j++; + btemp = intToBytes(ppoint.right!!.x.toInt()) + for (aBtemp in btemp) { + b[j] = aBtemp + j++ } - btemp = intToBytes(ppoint.right.y.intValue()); - for (byte aBtemp : btemp) { - b[j] = aBtemp; - j++; + btemp = intToBytes(ppoint.right!!.y.toInt()) + for (aBtemp in btemp) { + b[j] = aBtemp + j++ } } - saveFile(path, b); + saveFile(path, b) } - private static final String HEXES = "0123456789ABCDEF"; + private const val HEXES = "0123456789ABCDEF" - private static boolean isNull(Object obj) { - return obj == null; + private fun isNull(obj: Any?): Boolean { + return obj == null } - public static String showHexFromFile(String file) { - byte[] sourceBytes = getBytes(file); + @JvmStatic + fun showHexFromFile(file: String): String { + val sourceBytes = getBytes(file) if (isNull(sourceBytes)) { - return ""; + return "" } - return getHex(sourceBytes); + return FileUtils.getHex(sourceBytes!!) } - private static String getHex(byte[] raw) { - final StringBuilder hex = new StringBuilder(2 * raw.length); - for (final byte b : raw) { - hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); + private fun getHex(raw: ByteArray): String { + val hex = StringBuilder(2 * raw.size) + for (b in raw) { + hex.append(HEXES.get((b.toInt() and 0xF0) shr 4)) + .append(HEXES.get((b.toInt() and 0x0F))) } - return hex.toString(); + return hex.toString() } - public static List> loadPointsFromFile(String stringpath) { - byte[] rawData = getBytes(stringpath); - List> pair = new ArrayList<>(); - byte[] btemp = new byte[4]; - int f = 0, s; - Point point1 = new Point(); - point1.x = BigInteger.valueOf(1); - point1.y = BigInteger.valueOf(1); - Point point2 = new Point(); - point2.x = BigInteger.valueOf(1); - point2.y = BigInteger.valueOf(1); - for (int i = 0; i < (rawData != null ? rawData.length : 0); i++) { - btemp[i % 4] = rawData[i]; + @JvmStatic + fun loadPointsFromFile(stringpath: String): MutableList?> { + val rawData = getBytes(stringpath) + val pair: MutableList?> = ArrayList?>() + val btemp = ByteArray(4) + var f = 0 + var s: Int + var point1 = Point() + point1.x = BigInteger.valueOf(1) + point1.y = BigInteger.valueOf(1) + var point2 = Point() + point2.x = BigInteger.valueOf(1) + point2.y = BigInteger.valueOf(1) + for (i in 0..<(if (rawData != null) rawData.size else 0)) { + btemp[i % 4] = rawData!![i] if (i % 4 == 3) { if ((i / 4) % 4 == 0) { - f = bytesToInt(btemp); + f = bytesToInt(btemp) } if ((i / 4) % 4 == 1) { - s = bytesToInt(btemp); - point1 = new Point(); - point1.x = BigInteger.valueOf(f); - point1.y = BigInteger.valueOf(s); + s = bytesToInt(btemp) + point1 = Point() + point1.x = BigInteger.valueOf(f.toLong()) + point1.y = BigInteger.valueOf(s.toLong()) } if ((i / 4) % 4 == 2) { - f = bytesToInt(btemp); + f = bytesToInt(btemp) } if ((i / 4) % 4 == 3) { - s = bytesToInt(btemp); - point2 = new Point(); - point2.x = BigInteger.valueOf(f); - point2.y = BigInteger.valueOf(s); - pair.add(new Pair<>(point1, point2)); + s = bytesToInt(btemp) + point2 = Point() + point2.x = BigInteger.valueOf(f.toLong()) + point2.y = BigInteger.valueOf(s.toLong()) + pair.add(Pair(point1, point2)) } } } - return pair; + return pair } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e66ede9..f62b964 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -23,7 +23,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + android:backgroundTint="#0000FF" + android:text="RSA IMPLEMENTATION" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#0000FF" + android:text="ECCEG IMPLEMENTATION" + android:textColor="#FFFFFF" /> diff --git a/app/src/main/res/layout/fragment_eccegdecrypt.xml b/app/src/main/res/layout/fragment_eccegdecrypt.xml index d8c0d83..b8ad5b8 100644 --- a/app/src/main/res/layout/fragment_eccegdecrypt.xml +++ b/app/src/main/res/layout/fragment_eccegdecrypt.xml @@ -60,19 +60,15 @@ android:hint="Private Key Location" /> - + android:backgroundTint="#000CFF" + android:radius="10dp" + android:text="Search Private Key" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:radius="10dp" + android:text="Search Cipher Text" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:radius="10dp" + android:text="Decrypt" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:text="Search Public Key" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:text="Search Plain Text" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:radius="10dp" + android:text="Encrypt" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:text="Generate Key" + android:textColor="#FFFFFF" /> diff --git a/app/src/main/res/layout/fragment_rsadecrypt.xml b/app/src/main/res/layout/fragment_rsadecrypt.xml index d6e60d9..c530535 100644 --- a/app/src/main/res/layout/fragment_rsadecrypt.xml +++ b/app/src/main/res/layout/fragment_rsadecrypt.xml @@ -63,7 +63,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + android:backgroundTint="#0FF00F" + android:text="Search Private Key" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:text="Select File" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:text="Decrypt" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#0FF00F" + android:text="Search Public Key" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:text="Select File" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#000CFF" + android:text="Encrypt" + android:textColor="#FFFFFF" /> - + android:backgroundTint="#0FF00F" + android:text="Generate Key" + android:textColor="#FFFFFF" /> diff --git a/app/src/main/res/layout/layout_loading_dialog.xml b/app/src/main/res/layout/layout_loading_dialog.xml new file mode 100644 index 0000000..6039bcf --- /dev/null +++ b/app/src/main/res/layout/layout_loading_dialog.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8e5f458..7ccf86b 100644 --- a/build.gradle +++ b/build.gradle @@ -27,4 +27,4 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f6720ae..a7010d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -android.defaults.buildfeatures.buildconfig=true +android.buildfeatures.buildconfig=true android.enableJetifier=true android.nonFinalResIds=false android.nonTransitiveRClass=false diff --git a/settings.gradle b/settings.gradle index e7b4def..5d4cd5f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,9 @@ +pluginManagement { + plugins { + id 'org.jetbrains.kotlin.jvm' version '2.2.0' + } +} +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} include ':app' From f01f70b884d58b2ee85e98e1a8ff751ef42e22a6 Mon Sep 17 00:00:00 2001 From: Bervianto Leo Pratama Date: Sat, 2 Aug 2025 12:14:39 +0700 Subject: [PATCH 3/5] Rename .java to .kt --- .../{ECCEGDecryptFragment.java => ECCEGDecryptFragment.kt} | 0 .../{ECCEGEncryptFragment.java => ECCEGEncryptFragment.kt} | 0 ...{ECCEGGenerateKeyFragment.java => ECCEGGenerateKeyFragment.kt} | 0 .../fragment/{RSADecryptFragment.java => RSADecryptFragment.kt} | 0 .../fragment/{RSAEncryptFragment.java => RSAEncryptFragment.kt} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/{ECCEGDecryptFragment.java => ECCEGDecryptFragment.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/{ECCEGEncryptFragment.java => ECCEGEncryptFragment.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/{ECCEGGenerateKeyFragment.java => ECCEGGenerateKeyFragment.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/{RSADecryptFragment.java => RSADecryptFragment.kt} (100%) rename app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/{RSAEncryptFragment.java => RSAEncryptFragment.kt} (100%) diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.kt diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.kt similarity index 100% rename from app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.java rename to app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.kt From 61b09c85bb08d2894815ac4a44c031d0c0634ab5 Mon Sep 17 00:00:00 2001 From: Bervianto Leo Pratama Date: Sat, 2 Aug 2025 12:14:40 +0700 Subject: [PATCH 4/5] feat: another upgrade --- .idea/caches/deviceStreaming.xml | 329 +++++++++++++ app/build.gradle | 2 + .../ecceg_rsa_app/ECCEGActivity.kt | 30 +- .../ecceg_rsa_app/MainActivity.kt | 24 +- .../berviantoleo/ecceg_rsa_app/RSAActivity.kt | 38 +- .../adapter/ECCEGPagerAdapter.java | 53 -- .../adapter/RSAPagerAdapter.java | 53 -- .../fragment/ECCEGDecryptFragment.kt | 404 ++++++++------- .../fragment/ECCEGEncryptFragment.kt | 310 ++++++------ .../fragment/ECCEGGenerateKeyFragment.kt | 295 ++++++----- .../fragment/RSADecryptFragment.kt | 364 +++++++------- .../fragment/RSAEncryptFragment.kt | 458 +++++++++++------- .../fragment/RSAGenerateKeyFragment.kt | 184 +++---- .../berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt | 2 +- .../ecceg_rsa_app/lib/ecc/ECCEG.kt | 33 +- .../ecceg_rsa_app/lib/ecc/ECCEGMain.kt | 95 ++-- .../ecceg_rsa_app/lib/ecc/Point.kt | 6 +- .../berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt | 336 +++++++------ .../ecceg_rsa_app/utils/FileUtils.kt | 16 +- app/src/main/res/layout/activity_ecceg.xml | 2 +- app/src/main/res/layout/activity_rsa.xml | 2 +- build.gradle | 2 +- 22 files changed, 1833 insertions(+), 1205 deletions(-) delete mode 100644 app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/ECCEGPagerAdapter.java delete mode 100644 app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/RSAPagerAdapter.java diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml index 9aaec77..be3692c 100644 --- a/.idea/caches/deviceStreaming.xml +++ b/.idea/caches/deviceStreaming.xml @@ -644,6 +644,74 @@ diff --git a/app/build.gradle b/app/build.gradle index 71b8991..ff2de72 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,8 @@ dependencies { implementation 'io.github.ParkSangGwon:tedpermission-normal:3.4.2' implementation 'io.github.chochanaresh:filepicker:0.5.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.6.2' diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt index bca89f3..2070449 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/ECCEGActivity.kt @@ -3,26 +3,32 @@ package id.my.berviantoleo.ecceg_rsa_app import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.viewpager.widget.ViewPager -import butterknife.BindView -import butterknife.ButterKnife +// import butterknife.BindView // Removed ButterKnife import +// import butterknife.ButterKnife // Removed ButterKnife import import com.google.android.material.tabs.TabLayout import id.my.berviantoleo.ecceg_rsa_app.adapter.ECCEGPagerAdapter +import id.my.berviantoleo.ecceg_rsa_app.databinding.ActivityEccegBinding // Import ViewBinding class class ECCEGActivity : AppCompatActivity() { - @JvmField - @BindView(R.id.container_ecceg) - var mViewPager: ViewPager? = null + // @JvmField + // @BindView(R.id.container_ecceg) // Removed ButterKnife annotation + // var mViewPager: ViewPager? = null - @JvmField - @BindView(R.id.tabs_ecceg) - var tabLayout: TabLayout? = null + // @JvmField + // @BindView(R.id.tabs_ecceg) // Removed ButterKnife annotation + // var tabLayout: TabLayout? = null + + private lateinit var binding: ActivityEccegBinding // Declare binding variable override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_ecceg) - ButterKnife.bind(this) + binding = ActivityEccegBinding.inflate(layoutInflater) // Inflate layout + setContentView(binding.root) // Set content view to binding's root + // ButterKnife.bind(this) // Removed ButterKnife bind call + val mSectionsPagerAdapter = ECCEGPagerAdapter(supportFragmentManager) - mViewPager!!.adapter = mSectionsPagerAdapter - tabLayout!!.setupWithViewPager(mViewPager) + // Access views via binding object. Note: ViewPager and TabLayout are non-null from binding + binding.containerEcceg.adapter = mSectionsPagerAdapter + binding.tabsEcceg.setupWithViewPager(binding.containerEcceg) } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt index 066b6e7..438fcb8 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/MainActivity.kt @@ -3,24 +3,32 @@ package id.my.berviantoleo.ecceg_rsa_app import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import butterknife.ButterKnife -import butterknife.OnClick +import id.my.berviantoleo.ecceg_rsa_app.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - ButterKnife.bind(this) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.rsaButton.setOnClickListener { + launchRSA() + } + + binding.eccegButton.setOnClickListener { + launchECCEG() + } } - @OnClick(R.id.rsa_button) - fun launchRSA() { + private fun launchRSA() { val intent = Intent(this, RSAActivity::class.java) startActivity(intent) } - @OnClick(R.id.ecceg_button) - fun launchECCEG() { + private fun launchECCEG() { val intent = Intent(this, ECCEGActivity::class.java) startActivity(intent) } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt index 6c5139e..7fd3896 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/RSAActivity.kt @@ -2,28 +2,36 @@ package id.my.berviantoleo.ecceg_rsa_app import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.viewpager.widget.ViewPager -import butterknife.BindView -import butterknife.ButterKnife +import androidx.viewpager2.widget.ViewPager2 // Import ViewPager2 import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator // Import TabLayoutMediator import id.my.berviantoleo.ecceg_rsa_app.adapter.RSAPagerAdapter +import id.my.berviantoleo.ecceg_rsa_app.databinding.ActivityRsaBinding class RSAActivity : AppCompatActivity() { - /** - * The [ViewPager] that will host the section contents. - */ - @BindView(R.id.container) - var mViewPager: ViewPager? = null - @BindView(R.id.tabs) - var tabLayout: TabLayout? = null + private lateinit var binding: ActivityRsaBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_rsa) - ButterKnife.bind(this) - val mSectionsPagerAdapter = RSAPagerAdapter(supportFragmentManager) - mViewPager!!.adapter = mSectionsPagerAdapter - tabLayout!!.setupWithViewPager(mViewPager) + binding = ActivityRsaBinding.inflate(layoutInflater) + setContentView(binding.root) + + // The adapter now takes the Activity as an argument + val sectionsPagerAdapter = RSAPagerAdapter(this) + + // The container view is now a ViewPager2 + val viewPager: ViewPager2 = binding.container + viewPager.adapter = sectionsPagerAdapter + + // Setup TabLayout with ViewPager2 using TabLayoutMediator + val tabs: TabLayout = binding.tabs + TabLayoutMediator(tabs, viewPager) { tab, position -> + tab.text = when (position) { + 0 -> "Generate Key" + 1 -> "Encrypt" + else -> "Decrypt" // Position 2 or any other + } + }.attach() } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/ECCEGPagerAdapter.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/ECCEGPagerAdapter.java deleted file mode 100644 index 04d4f2c..0000000 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/ECCEGPagerAdapter.java +++ /dev/null @@ -1,53 +0,0 @@ -package id.my.berviantoleo.ecceg_rsa_app.adapter; - -import id.my.berviantoleo.ecceg_rsa_app.fragment.ECCEGDecryptFragment; -import id.my.berviantoleo.ecceg_rsa_app.fragment.ECCEGEncryptFragment; -import id.my.berviantoleo.ecceg_rsa_app.fragment.ECCEGGenerateKeyFragment; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; - -/** - * Created by bervianto on 3/27/18. - */ - -public class ECCEGPagerAdapter extends FragmentPagerAdapter { - - public ECCEGPagerAdapter(FragmentManager fm) { - super(fm); - } - - @NonNull - @Override - public Fragment getItem(int position) { - // getItem is called to instantiate the fragment for the given page. - // Return a PlaceholderFragment (defined as a static inner class below). - if (position == 0) { - return ECCEGGenerateKeyFragment.newInstance(); - } else if (position == 1) { - return ECCEGEncryptFragment.newInstance(); - } else { - return ECCEGDecryptFragment.newInstance(); - } - } - - @Override - public CharSequence getPageTitle(int position) { - if (position == 0) { - return "Generate Key"; - } else if (position == 1) { - return "Encrypt"; - } else { - return "Decrypt"; - } - } - - @Override - public int getCount() { - return 3; - } -} - - diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/RSAPagerAdapter.java b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/RSAPagerAdapter.java deleted file mode 100644 index b25cf09..0000000 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/adapter/RSAPagerAdapter.java +++ /dev/null @@ -1,53 +0,0 @@ -package id.my.berviantoleo.ecceg_rsa_app.adapter; - -import id.my.berviantoleo.ecceg_rsa_app.fragment.RSADecryptFragment; -import id.my.berviantoleo.ecceg_rsa_app.fragment.RSAEncryptFragment; -import id.my.berviantoleo.ecceg_rsa_app.fragment.RSAGenerateKeyFragment; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; - -/** - * Created by bervianto on 3/27/18. - */ - -public class RSAPagerAdapter extends FragmentPagerAdapter { - - public RSAPagerAdapter(FragmentManager fm) { - super(fm); - } - - @NonNull - @Override - public Fragment getItem(int position) { - // getItem is called to instantiate the fragment for the given page. - // Return a PlaceholderFragment (defined as a static inner class below). - if (position == 0) { - return RSAGenerateKeyFragment.newInstance(); - } else if (position == 1) { - return RSAEncryptFragment.newInstance(); - } else { - return RSADecryptFragment.newInstance(); - } - } - - @Override - public CharSequence getPageTitle(int position) { - if (position == 0) { - return "Generate Key"; - } else if (position == 1) { - return "Encrypt"; - } else { - return "Decrypt"; - } - } - - @Override - public int getCount() { - return 3; - } -} - - diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.kt index 6e30b80..71b62e3 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGDecryptFragment.kt @@ -1,204 +1,258 @@ -package id.my.berviantoleo.ecceg_rsa_app.fragment; - -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; - -import com.google.android.material.textfield.TextInputEditText; -import com.obsez.android.lib.filechooser.ChooserDialog; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.math.BigInteger; -import java.util.List; -import java.util.Objects; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cc.cloudist.acplibrary.ACProgressFlower; -import id.my.berviantoleo.ecceg_rsa_app.R; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECC; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECCEG; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Pair; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Point; -import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils; - -import static android.os.Environment.getExternalStorageDirectory; - - -public class ECCEGDecryptFragment extends Fragment { - - @BindView(R.id.private_key_loc_ecceg_value) - protected TextInputEditText privateKeyLoc; - @BindView(R.id.cipher_decrypt_text_loc_value_ecceg) - protected TextInputEditText cipherTextLoc; - @BindView(R.id.decrypt_text_loc_ecceg) - protected TextInputEditText decryptTextLoc; - @BindView(R.id.input_file_decrypt_ecceg) - protected TextInputEditText inputContent; - @BindView(R.id.output_file_decrypt_ecceg) - protected TextInputEditText outputContent; - @BindView(R.id.input_file_size_decrypt_ecceg) - protected TextInputEditText inputSize; - @BindView(R.id.output_file_size_decrypt_ecceg) - protected TextInputEditText outputSize; - @BindView(R.id.time_decrypt_ecceg) - protected TextInputEditText timeElapsed; - @BindView(R.id.a_decrypt_ecceg_value) - protected TextInputEditText a; - @BindView(R.id.b_decrypt_ecceg_value) - protected TextInputEditText b; - @BindView(R.id.p_decrypt_ecceg_value) - protected TextInputEditText p; - private ACProgressFlower loadingView; - private long startTime; - private long endTime; - - - public ECCEGDecryptFragment() { - // Required empty public constructor - } +package id.my.berviantoleo.ecceg_rsa_app.fragment + +import android.os.Bundle +import android.os.Environment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import cc.cloudist.acplibrary.ACProgressFlower +import com.developer.filepicker.model.DialogConfigs +import com.developer.filepicker.model.DialogProperties +import com.developer.filepicker.view.FilePickerDialog +import id.my.berviantoleo.ecceg_rsa_app.R +import id.my.berviantoleo.ecceg_rsa_app.databinding.FragmentEccegdecryptBinding +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECC +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECCEG +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Point +import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.math.BigInteger + +class ECCEGDecryptFragment : Fragment() { - public static ECCEGDecryptFragment newInstance() { - return new ECCEGDecryptFragment(); + private var _binding: FragmentEccegdecryptBinding? = null + private val binding get() = _binding!! + + private var loadingView: ACProgressFlower? = null + private var privateKeyPath: String? = null + private var cipherTextPath: String? = null + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentEccegdecryptBinding.inflate(inflater, container, false) + return binding.root } - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_eccegdecrypt, container, false); - ButterKnife.bind(this, view); - loadingView = new ACProgressFlower.Builder(getContext()).build(); - loadingView.setCanceledOnTouchOutside(false); - loadingView.setCancelable(false); - return view; + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + loadingView = ACProgressFlower.Builder(context) + .direction(ACProgressFlower.Direction.CLOCKWISE) + .themeColor(resources.getColor(R.color.colorPrimary, requireActivity().theme)) + .fadeColor(resources.getColor(R.color.colorAccent, requireActivity().theme)) + .build() + loadingView?.setCancelable(false) + loadingView?.setCanceledOnTouchOutside(false) + + binding.searchPrivateKeyEccegDecrypt.setOnClickListener { + selectPrivateKeyFile() + } + + binding.searchCipherTextEccegDecrypt.setOnClickListener { + selectCipherTextFile() + } + + binding.decryptButtonEcceg.setOnClickListener { + decryptData() + } } - @OnClick(R.id.decrypt_button_ecceg) - void decrypt() { - if (!Objects.requireNonNull(privateKeyLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(decryptTextLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(cipherTextLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(a.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(b.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(p.getText()).toString().equalsIgnoreCase("")) { - File file = Environment.getExternalStorageDirectory(); - File location = new File(file, "ECCEG/"); - if (!location.exists()) { - location.mkdir(); - } - String decryptLoc = location.getAbsolutePath() + "/" + decryptTextLoc.getText().toString(); - new ECCEGDecryptFragment.Decrypt(ECCEGDecryptFragment.this).execute( - a.getText().toString(), - b.getText().toString(), - p.getText().toString(), - privateKeyLoc.getText().toString(), - cipherTextLoc.getText().toString(), - decryptLoc - ); + private fun selectPrivateKeyFile() { + val properties = DialogProperties().apply { + selection_mode = DialogConfigs.SINGLE_MODE + selection_type = DialogConfigs.FILE_SELECT + root = Environment.getExternalStorageDirectory() + error_dir = File(DialogConfigs.DEFAULT_DIR) + offset = File(DialogConfigs.DEFAULT_DIR) + extensions = null // Allow all file types or specify if needed + show_hidden_files = false } + val dialog = FilePickerDialog(context, properties) + dialog.setTitle("Select Private Key File") + dialog.setDialogSelectionListener { files -> + if (files.isNotEmpty()) { + privateKeyPath = files[0] + binding.privateKeyLocEccegValue.setText(privateKeyPath) + } + } + dialog.show() } - @OnClick(R.id.search_private_key_ecceg) - void openPrivateKey() { - new ChooserDialog(getActivity()) - .withFilter(false, false, "pri") - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> privateKeyLoc.setText(pathFile.getPath())) - .build() - .show(); + private fun selectCipherTextFile() { + val properties = DialogProperties().apply { + selection_mode = DialogConfigs.SINGLE_MODE + selection_type = DialogConfigs.FILE_SELECT + root = Environment.getExternalStorageDirectory() + error_dir = File(DialogConfigs.DEFAULT_DIR) + offset = File(DialogConfigs.DEFAULT_DIR) + extensions = null // Allow all file types or specify if needed + show_hidden_files = false + } + val dialog = FilePickerDialog(context, properties) + dialog.setTitle("Select Cipher Text File") + dialog.setDialogSelectionListener { files -> + if (files.isNotEmpty()) { + cipherTextPath = files[0] + binding.cipherTextLocEccegValue.setText(cipherTextPath) + lifecycleScope.launch { + loadCipherTextFileContent(cipherTextPath!!) + } + } + } + dialog.show() } - @OnClick(R.id.search_cipher_text_ecceg) - void searchCipherText() { - new ChooserDialog(getActivity()) - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> { - cipherTextLoc.setText(pathFile.getPath()); - inputSize.setText(String.valueOf(pathFile.length())); - loadingView.show(); - new SetInput(ECCEGDecryptFragment.this).execute(pathFile.getPath()); - }) - .build() - .show(); + private suspend fun loadCipherTextFileContent(filePath: String) { + withContext(Dispatchers.Main) { + loadingView?.show() + } + try { + val fileContentHex = withContext(Dispatchers.IO) { + FileUtils.showHexFromFile(filePath) + } + val fileSize = withContext(Dispatchers.IO) { + File(filePath).length().toString() + } + withContext(Dispatchers.Main) { + binding.inputFileDecryptEcceg.setText(fileContentHex) + binding.inputFileSizeDecryptEcceg.setText(fileSize) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText(context, "Error reading cipher text file: ${e.message}", Toast.LENGTH_LONG).show() + } + } finally { + withContext(Dispatchers.Main) { + loadingView?.dismiss() + } + } } - private class Decrypt extends AsyncTask { + private fun decryptData() { + val privateKeyLocation = binding.privateKeyLocEccegValue.text.toString() + val cipherTextLocation = binding.cipherTextLocEccegValue.text.toString() + val plainTextSaveLocation = binding.plainTextLocEccegValue.text.toString() + val aStr = binding.aDecryptEccegValue.text.toString() + val bStr = binding.bDecryptEccegValue.text.toString() + val pStr = binding.pDecryptEccegValue.text.toString() - // only retain a weak reference to the activity - Decrypt(ECCEGDecryptFragment context) { - new WeakReference<>(context); + if (privateKeyLocation.isBlank() || cipherTextLocation.isBlank() || plainTextSaveLocation.isBlank() || + aStr.isBlank() || bStr.isBlank() || pStr.isBlank() + ) { + Toast.makeText(context, "Please fill all required fields", Toast.LENGTH_SHORT).show() + return } - @Override - protected String doInBackground(String... strings) { - startTime = System.currentTimeMillis(); - try { - ECC ecc = new ECC(); - ecc.a = new BigInteger(strings[0]); - ecc.b = new BigInteger(strings[1]); - ecc.p = new BigInteger(strings[2]); - ecc.k = BigInteger.valueOf(30); - ECCEG ecceg = new ECCEG(ecc, ecc.getBasePoint()); - ecceg.loadPrivateKey(strings[3]); - List> read_enc = FileUtils.loadPointsFromFile(strings[4]); - List read_dec = ecceg.decrypt(read_enc); - StringBuilder stringBuilder = new StringBuilder(); - for (Point pp : read_dec) - stringBuilder.append((char) ecc.pointToInt(pp).byteValue()); - endTime = System.currentTimeMillis(); - FileUtils.saveFile(strings[5], stringBuilder.toString().getBytes()); - return stringBuilder.toString(); - } catch (Exception e) { - Log.e("Decrypt", e.getMessage()); - return "failed"; + try { + val a = BigInteger(aStr) + val b = BigInteger(bStr) + val p = BigInteger(pStr) + + lifecycleScope.launch { + performDecryption(privateKeyLocation, cipherTextLocation, plainTextSaveLocation, a, b, p) } - } - @Override - protected void onPostExecute(String hex) { - timeElapsed.setText(String.valueOf(endTime - startTime)); - outputContent.setText(hex); - File file = new File(Environment.getExternalStorageDirectory().getPath() + "/ECCEG/" + decryptTextLoc.getText().toString()); - outputSize.setText(String.valueOf(file.length())); - loadingView.dismiss(); - Toast.makeText(getActivity(), "Finished Encrypt", Toast.LENGTH_SHORT).show(); + } catch (e: NumberFormatException) { + Toast.makeText(context, "Invalid ECC parameters (a, b, or p). Please enter valid numbers.", Toast.LENGTH_LONG).show() } } - private class SetInput extends AsyncTask { - - // only retain a weak reference to the activity - SetInput(ECCEGDecryptFragment context) { - new WeakReference<>(context); + private suspend fun performDecryption( + privateKeyPath: String, + cipherTextPath: String, + plainTextSavePath: String, + a: BigInteger, + b: BigInteger, + p: BigInteger + ) { + withContext(Dispatchers.Main) { + loadingView?.show() } + var resultMessage = "Decryption Failed" + var plainTextHex: String? = null + var outputFileSize: String? = null + var executionTime: String? = null - @Override - protected String doInBackground(String... strings) { - if (strings.length == 1) { - return FileUtils.showHexFromFile(strings[0]); + try { + val ecc = ECC().apply { + this.a = a + this.b = b + this.p = p } - return ""; - } - @Override - protected void onPostExecute(String s) { - inputContent.setText(s); - loadingView.dismiss(); + val privateKey = withContext(Dispatchers.IO) { + FileUtils.getPrivateKeyFromFile(privateKeyPath) + } + if (privateKey == null) { + resultMessage = "Failed to read private key." + throw Exception(resultMessage) + } + + val cipherPoints = withContext(Dispatchers.IO) { + FileUtils.getPointsFromFile(cipherTextPath) + } + if (cipherPoints == null || cipherPoints.isEmpty()) { + resultMessage = "Failed to read cipher text or cipher text is empty." + throw Exception(resultMessage) + } + + val startTime = System.nanoTime() + val decryptedBytes = withContext(Dispatchers.IO) { + ECCEG.decrypt(cipherPoints, privateKey, ecc) + } + val endTime = System.nanoTime() + executionTime = ((endTime - startTime) / 1e6).toString() + " ms" + + + val newDir = File(Environment.getExternalStorageDirectory(), "/ECCEG_DECRYPTED/") + if (!newDir.exists()) { + newDir.mkdirs() + } + val fullSavePath = newDir.absolutePath + "/" + plainTextSavePath + + withContext(Dispatchers.IO) { + FileUtils.saveBytesToFile(decryptedBytes, fullSavePath) + } + + plainTextHex = withContext(Dispatchers.IO) { + FileUtils.showHexFromFile(fullSavePath) + } + outputFileSize = withContext(Dispatchers.IO) { + File(fullSavePath).length().toString() + } + resultMessage = "Decryption Successful! Saved to $fullSavePath" + + } catch (e: Exception) { + resultMessage = "Decryption Error: ${e.message}" + e.printStackTrace() + } finally { + withContext(Dispatchers.Main) { + loadingView?.dismiss() + binding.outputFileDecryptEcceg.setText(plainTextHex ?: "Error") + binding.outputFileSizeDecryptEcceg.setText(outputFileSize ?: "N/A") + binding.timeDecryptEcceg.setText(executionTime ?: "N/A") + Toast.makeText(context, resultMessage, Toast.LENGTH_LONG).show() + } } } + override fun onDestroyView() { + super.onDestroyView() + loadingView?.dismiss() // Ensure dialog is dismissed + _binding = null + } + companion object { + fun newInstance(): ECCEGDecryptFragment { + return ECCEGDecryptFragment() + } + } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.kt index f76be7f..c762cbb 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGEncryptFragment.kt @@ -1,217 +1,233 @@ -package id.my.berviantoleo.ecceg_rsa_app.fragment; - - -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; - -import com.google.android.material.textfield.TextInputEditText; -import com.obsez.android.lib.filechooser.ChooserDialog; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.math.BigInteger; -import java.util.List; -import java.util.Objects; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cc.cloudist.acplibrary.ACProgressFlower; -import id.my.berviantoleo.ecceg_rsa_app.R; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECC; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECCEG; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Pair; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Point; -import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA; -import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils; - -import static android.os.Environment.getExternalStorageDirectory; +package id.my.berviantoleo.ecceg_rsa_app.fragment + + +import android.view.View +import androidx.fragment.app.Fragment +import com.obsez.android.lib.filechooser.ChooserDialog +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Pair +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Point +import java.io.File +import java.lang.String +import java.lang.ref.WeakReference +import java.math.BigInteger +import java.util.Objects +import kotlin.ByteArray +import kotlin.Exception +import kotlin.Int +import kotlin.Long +import kotlin.collections.MutableList +import kotlin.collections.plus +import kotlin.plus +import kotlin.sequences.plus +import kotlin.text.equals +import kotlin.text.plus +import kotlin.toString /** - * A simple {@link Fragment} subclass. - * Use the {@link ECCEGEncryptFragment#newInstance} factory method to + * A simple [Fragment] subclass. + * Use the [ECCEGEncryptFragment.newInstance] factory method to * create an instance of this fragment. */ -public class ECCEGEncryptFragment extends Fragment { - +class ECCEGEncryptFragment : Fragment() { + @JvmField @BindView(R.id.public_key_loc_ecceg_value) - protected TextInputEditText publicKeyLoc; + var publicKeyLoc: TextInputEditText? = null + @JvmField @BindView(R.id.plain_text_loc_value_ecceg) - protected TextInputEditText plainTextLoc; + var plainTextLoc: TextInputEditText? = null + @JvmField @BindView(R.id.cipher_text_loc_ecceg) - protected TextInputEditText cipherTextLoc; + var cipherTextLoc: TextInputEditText? = null + @JvmField @BindView(R.id.input_file_encrypt_ecceg) - protected TextInputEditText inputContent; + var inputContent: TextInputEditText? = null + @JvmField @BindView(R.id.output_file_encrypt_ecceg) - protected TextInputEditText outputContent; + var outputContent: TextInputEditText? = null + @JvmField @BindView(R.id.input_file_size_encrypt_ecceg) - protected TextInputEditText inputSize; + var inputSize: TextInputEditText? = null + @JvmField @BindView(R.id.output_file_size_encrypt_ecceg) - protected TextInputEditText outputSize; + var outputSize: TextInputEditText? = null + @JvmField @BindView(R.id.time_encrypt_ecceg) - protected TextInputEditText timeElapsed; + var timeElapsed: TextInputEditText? = null + @JvmField @BindView(R.id.a_encrypt_ecceg_value) - protected TextInputEditText a; + var a: TextInputEditText? = null + @JvmField @BindView(R.id.b_encrypt_ecceg_value) - protected TextInputEditText b; + var b: TextInputEditText? = null + @JvmField @BindView(R.id.p_encrypt_ecceg_value) - protected TextInputEditText p; - - private ACProgressFlower loadingView; - private long startTime; - private long endTime; + var p: TextInputEditText? = null - public ECCEGEncryptFragment() { - // Required empty public constructor - } - - public static ECCEGEncryptFragment newInstance() { - return new ECCEGEncryptFragment(); - } + private var loadingView: ACProgressFlower? = null + private var startTime: Long = 0 + private var endTime: Long = 0 - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_eccegencrypt, container, false); - ButterKnife.bind(this, view); - loadingView = new ACProgressFlower.Builder(getContext()).build(); - loadingView.setCanceledOnTouchOutside(false); - loadingView.setCancelable(false); - return view; + val view: View = inflater.inflate(R.layout.fragment_eccegencrypt, container, false) + ButterKnife.bind(this, view) + loadingView = Builder(getContext()).build() + loadingView.setCanceledOnTouchOutside(false) + loadingView.setCancelable(false) + return view } @OnClick(R.id.encrypt_button_ecceg) - void encrypt() { - if (!Objects.requireNonNull(publicKeyLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(plainTextLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(cipherTextLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(a.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(b.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(p.getText()).toString().equalsIgnoreCase("")) { - File file = Environment.getExternalStorageDirectory(); - File location = new File(file, "ECCEG/"); + fun encrypt() { + if (!Objects.requireNonNull(publicKeyLoc.getText()).toString().equals( + "", + ignoreCase = true + ) && !Objects.requireNonNull(plainTextLoc.getText()).toString().equals( + "", + ignoreCase = true + ) && !Objects.requireNonNull(cipherTextLoc.getText()).toString() + .equals("", ignoreCase = true) && !Objects.requireNonNull(a.getText()) + .toString() + .equals("", ignoreCase = true) && !Objects.requireNonNull(b.getText()) + .toString() + .equals("", ignoreCase = true) && !Objects.requireNonNull(p.getText()) + .toString().equals("", ignoreCase = true) + ) { + val file: File? = Environment.getExternalStorageDirectory() + val location = File(file, "ECCEG/") if (!location.exists()) { - location.mkdir(); + location.mkdir() } - String cipherLoc = location.getAbsolutePath() + "/" + cipherTextLoc.getText().toString(); - new ECCEGEncryptFragment.Encrypt(ECCEGEncryptFragment.this).execute( + val cipherLoc = location.getAbsolutePath() + "/" + cipherTextLoc.getText().toString() + id.my.berviantoleo.ecceg_rsa_app.fragment.ECCEGEncryptFragment.Encrypt(this@ECCEGEncryptFragment) + .execute( a.getText().toString(), b.getText().toString(), p.getText().toString(), publicKeyLoc.getText().toString(), plainTextLoc.getText().toString(), cipherLoc - ); + ) } } @OnClick(R.id.search_public_key_ecceg) - void openPublicKey() { - new ChooserDialog(getActivity()) - .withFilter(false, false, "pub") - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> publicKeyLoc.setText(pathFile.getPath())) - .build() - .show(); + fun openPublicKey() { + ChooserDialog(getActivity()) + .withFilter(false, false, "pub") + .withStartFile(Environment.getExternalStorageDirectory().getAbsolutePath()) + .withResources( + R.string.title_choose_file, + R.string.title_choose, + R.string.dialog_cancel + ) + .withChosenListener({ path, pathFile -> publicKeyLoc.setText(pathFile.getPath()) }) + .build() + .show() } @OnClick(R.id.search_plain_text_ecceg) - void searchPlainText() { - new ChooserDialog(getActivity()) - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> { - plainTextLoc.setText(pathFile.getPath()); - inputSize.setText(String.valueOf(pathFile.length())); - loadingView.show(); - new SetInput(ECCEGEncryptFragment.this).execute(pathFile.getPath()); - }) - .build() - .show(); + fun searchPlainText() { + ChooserDialog(getActivity()) + .withStartFile(Environment.getExternalStorageDirectory().getAbsolutePath()) + .withResources( + R.string.title_choose_file, + R.string.title_choose, + R.string.dialog_cancel + ) + .withChosenListener({ path, pathFile -> + plainTextLoc.setText(pathFile.getPath()) + inputSize.setText(String.valueOf(pathFile.length())) + loadingView.show() + id.my.berviantoleo.ecceg_rsa_app.fragment.ECCEGEncryptFragment.SetInput(this@ECCEGEncryptFragment) + .execute(pathFile.getPath()) + }) + .build() + .show() } - private class Encrypt extends AsyncTask { - + private inner class Encrypt(context: ECCEGEncryptFragment?) : + AsyncTask() { // only retain a weak reference to the activity - Encrypt(ECCEGEncryptFragment context) { - new WeakReference<>(context); + init { + WeakReference(context) } - @Override - protected String doInBackground(String... strings) { - startTime = System.currentTimeMillis(); + override fun doInBackground(vararg strings: kotlin.String?): kotlin.String { + startTime = System.currentTimeMillis() try { - ECC ecc = new ECC(); - ecc.a = new BigInteger(strings[0]); - ecc.b = new BigInteger(strings[1]); - ecc.p = new BigInteger(strings[2]); - ecc.k = BigInteger.valueOf(30); - ECCEG ecceg = new ECCEG(ecc, ecc.getBasePoint()); - ecceg.loadPublicKey(strings[3]); - byte[] read = FileUtils.getBytes(strings[4]); - List> enc = ecceg.encryptBytes(read); - FileUtils.savePointsToFile(strings[5], enc); - endTime = System.currentTimeMillis(); - return FileUtils.showHexFromFile(strings[5]); - } catch (Exception e) { - return "failed"; + val ecc: ECC = ECC() + ecc.a = BigInteger(strings[0]) + ecc.b = BigInteger(strings[1]) + ecc.p = BigInteger(strings[2]) + ecc.k = BigInteger.valueOf(30) + val ecceg: ECCEG = ECCEG(ecc, ecc.basePoint) + ecceg.loadPublicKey(strings[3]) + val read: ByteArray? = + id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils.getBytes(strings[4]) + val enc: MutableList?> = ecceg.encryptBytes(read) + id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils.savePointsToFile(strings[5], enc) + endTime = System.currentTimeMillis() + return id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils.showHexFromFile(strings[5]) + } catch (e: Exception) { + return "failed" } } - @Override - protected void onPostExecute(String hex) { - timeElapsed.setText(String.valueOf(endTime - startTime)); - outputContent.setText(hex); - File file = new File(Environment.getExternalStorageDirectory().getPath() + "/ECCEG/" + Objects.requireNonNull(cipherTextLoc.getText()).toString()); - outputSize.setText(String.valueOf(file.length())); - loadingView.dismiss(); - Toast.makeText(getActivity(), "Finished Encrypt", Toast.LENGTH_SHORT).show(); + override fun onPostExecute(hex: kotlin.String?) { + timeElapsed.setText((endTime - startTime).toString()) + outputContent.setText(hex) + val file: File = File( + Environment.getExternalStorageDirectory() + .getPath() + "/ECCEG/" + Objects.requireNonNull(cipherTextLoc.getText()) + .toString() + ) + outputSize.setText(file.length().toString()) + loadingView.dismiss() + Toast.makeText(getActivity(), "Finished Encrypt", Toast.LENGTH_SHORT).show() } } - private class SetInput extends AsyncTask { - + private inner class SetInput(context: ECCEGEncryptFragment?) : + AsyncTask() { // only retain a weak reference to the activity - SetInput(ECCEGEncryptFragment context) { - new WeakReference<>(context); + init { + WeakReference(context) } - @Override - protected String doInBackground(String... strings) { - if (strings.length == 1) { - byte[] bytes = RSA.getBytes(strings[0]); + override fun doInBackground(vararg strings: kotlin.String?): kotlin.String { + if (strings.size == 1) { + val bytes: ByteArray? = + id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA.getBytes(strings[0]) if (bytes != null) { - return new String(bytes); + return String(bytes) } } - return ""; + return "" } - @Override - protected void onPostExecute(String s) { - inputContent.setText(s); - loadingView.dismiss(); + override fun onPostExecute(s: kotlin.String?) { + inputContent.setText(s) + loadingView.dismiss() } } + companion object { + fun newInstance(): ECCEGEncryptFragment { + return ECCEGEncryptFragment() + } + } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.kt index 654145d..8157d4e 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/ECCEGGenerateKeyFragment.kt @@ -1,133 +1,200 @@ -package id.my.berviantoleo.ecceg_rsa_app.fragment; - -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; - -import com.google.android.material.textfield.TextInputEditText; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.math.BigInteger; -import java.util.Objects; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cc.cloudist.acplibrary.ACProgressFlower; -import id.my.berviantoleo.ecceg_rsa_app.R; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECC; -import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECCEGMain; - - -public class ECCEGGenerateKeyFragment extends Fragment { - - - @BindView(R.id.eccegA) - protected TextInputEditText a; - @BindView(R.id.eccegB) - protected TextInputEditText b; - @BindView(R.id.eccegP) - protected TextInputEditText p; - @BindView(R.id.eccegPrivatePath) - protected TextInputEditText pri; - @BindView(R.id.eccegPubPath) - protected TextInputEditText pub; - private ACProgressFlower loadingView; - - public ECCEGGenerateKeyFragment() { - // Required empty public constructor +package id.my.berviantoleo.ecceg_rsa_app.fragment + +import android.Manifest +import android.app.AlertDialog +import android.os.Bundle +import android.os.Environment +import android.text.Editable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.gun0912.tedpermission.PermissionListener +import com.gun0912.tedpermission.normal.TedPermission +import id.my.berviantoleo.ecceg_rsa_app.R +import id.my.berviantoleo.ecceg_rsa_app.databinding.FragmentEcceggenerateKeyBinding +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECC +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.ECCEG +import id.my.berviantoleo.ecceg_rsa_app.lib.ecc.Point +import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.math.BigInteger + +class ECCEGGenerateKeyFragment : Fragment() { + + private var _binding: FragmentEcceggenerateKeyBinding? = null + private val binding get() = _binding!! + + private var loadingDialog: AlertDialog? = null + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentEcceggenerateKeyBinding.inflate(inflater, container, false) + return binding.root } - public static ECCEGGenerateKeyFragment newInstance() { - return new ECCEGGenerateKeyFragment(); - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupLoadingDialog() - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + binding.generateKeyEcceg.setOnClickListener { + validateAndRequestPermissions() + } } - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_ecceggenerate_key, container, false); - ButterKnife.bind(this, view); - loadingView = new ACProgressFlower.Builder(getContext()).build(); - loadingView.setCanceledOnTouchOutside(false); - loadingView.setCancelable(false); - return view; + private fun setupLoadingDialog() { + val builder = AlertDialog.Builder(requireContext()) + builder.setCancelable(false) + builder.setView(R.layout.layout_loading_dialog) + loadingDialog = builder.create() } - @OnClick(R.id.generate_key_ecceg) - void generateKeyECCEG() { - if (!Objects.requireNonNull(a.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(b.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(p.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(pub.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(pri.getText()).toString().equalsIgnoreCase("")) { - loadingView.show(); - new ECCEGGenerateKeyFragment.GenerateKey(ECCEGGenerateKeyFragment.this).execute( - a.getText().toString(), - b.getText().toString(), - p.getText().toString(), - pub.getText().toString(), - pri.getText().toString() - ); + private fun validateAndRequestPermissions() { + val aStr = binding.eccegA.text.toString() + val bStr = binding.eccegB.text.toString() + val pStr = binding.eccegP.text.toString() + val baseXStr = binding.eccegBasePointX.text.toString() + val baseYStr = binding.eccegBasePointY.text.toString() + val kStr = binding.eccegKVal.text.toString() + val pubPath = binding.eccegPubPath.text.toString() + val privPath = binding.eccegPrivatePath.text.toString() + + if (aStr.isBlank() || bStr.isBlank() || pStr.isBlank() || + baseXStr.isBlank() || baseYStr.isBlank() || kStr.isBlank() || + pubPath.isBlank() || privPath.isBlank() + ) { + Toast.makeText(context, "All fields must be filled", Toast.LENGTH_SHORT).show() + return } - } - private class GenerateKey extends AsyncTask { - - private WeakReference activityReference; - - // only retain a weak reference to the activity - GenerateKey(ECCEGGenerateKeyFragment context) { - activityReference = new WeakReference<>(context); + try { + BigInteger(aStr) + BigInteger(bStr) + BigInteger(pStr) + BigInteger(baseXStr) + BigInteger(baseYStr) + BigInteger(kStr) + } catch (e: NumberFormatException) { + Toast.makeText(context, "Invalid number format for ECC parameters or K", Toast.LENGTH_SHORT).show() + return } + TedPermission.create() + .setPermissionListener(object : PermissionListener { + override fun onPermissionGranted() { + generateAndSaveKeys(aStr, bStr, pStr, baseXStr, baseYStr, kStr, pubPath, privPath) + } + + override fun onPermissionDenied(deniedPermissions: List) { + Toast.makeText( + requireContext(), + "Permission Denied\n" + deniedPermissions.joinToString("\n"), + Toast.LENGTH_SHORT + ).show() + } + }) + .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]") + .setPermissions( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + .check() + } - @Override - protected String doInBackground(String... strings) { - BigInteger a = new BigInteger(strings[0]); - BigInteger b = new BigInteger(strings[1]); - BigInteger p = new BigInteger(strings[2]); - ECC ecc = new ECC(); - ecc.a = a; - ecc.b = b; - ecc.p = p; - File file = Environment.getExternalStorageDirectory(); - File newDir = new File(file, "/ECCEG/"); - if (!newDir.exists()) { - newDir.mkdir(); - } - String privateLocation = newDir.getAbsolutePath() + "/" + strings[4]; - String publicLocation = newDir.getAbsolutePath() + "/" + strings[3]; + private fun generateAndSaveKeys( + aStr: String, bStr: String, pStr: String, + baseXStr: String, baseYStr: String, kStr: String, + pubPath: String, privPath: String + ) { + loadingDialog?.show() + lifecycleScope.launch { try { - ECCEGMain.generateKey(ecc, privateLocation, publicLocation); - return "finished"; - } catch (Exception e) { - return "exception"; + val result = withContext(Dispatchers.IO) { + val a = BigInteger(aStr) + val b = BigInteger(bStr) + val p = BigInteger(pStr) + val baseX = BigInteger(baseXStr) + val baseY = BigInteger(baseYStr) + val privateKeyK = BigInteger(kStr) // This is the private key + + val ecc = ECC() + ecc.a = a + ecc.b = b + ecc.p = p + ecc.basePoint = Point(baseX, baseY) + // k is used for point multiplication factor in standard ECC, + // but here kStr is directly the private key. + // The ECCEG library might generate the public key from this private key. + + // Assuming ECCEG constructor takes ECC object and a base point for operations. + // And there's a way to set or use the private key `privateKeyK` + // to generate the corresponding public key. + + val ecceg = ECCEG(ecc, ecc.basePoint) + + // This part is crucial and depends on your ECCEG library: + // How do you get the public key if you provide the private key `privateKeyK`? + // Option 1: A method to generate public key from private key + // val publicKey = ecceg.generatePublicKeyFromPrivateKey(privateKeyK) + + // Option 2: If ECCEG directly uses a private key you set to derive public key. + // For example, if your ECCEG class has a method like `setPrivateKeyAndGeneratePublicKey()` + // or if the public key is generated when you set the private key. + // For now, let's assume `ecceg.generateKey()` is smart enough or there's another method. + // If `generateKey()` randomly generates a new key pair, that's not what we want here as K is given. + + // Let's assume your ECCEG class can take a private key and expose the public key. + // This is a placeholder for how your library might work. + // You might need to directly use `privateKeyK` with `ecc.multiply` + // to get the public key point if `ECCEG` doesn't handle it. + val publicKeyPoint = ecc.multiply(ecc.basePoint, privateKeyK) + + if (publicKeyPoint == null) { + return@withContext "Failed to generate public key point. Point at infinity?" + } + + val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + val eccKeysDir = File(downloadsDir, "ECCEG_KEYS") + if (!eccKeysDir.exists()) { + eccKeysDir.mkdirs() + } + + val privateKeyFile = File(eccKeysDir, privPath) + val publicKeyFile = File(eccKeysDir, pubPath) + + // Save the private key (BigInteger k) + FileUtils.savePrivateKeyToFile(privateKeyFile.absolutePath, privateKeyK.toString()) // Assuming it saves string + // Save the public key (Point) + FileUtils.savePublicKeyToFile(publicKeyFile.absolutePath, publicKeyPoint) // Assuming it saves Point + + "Keys generated and saved successfully\nPrivate Key: ${privateKeyFile.absolutePath}\nPublic Key: ${publicKeyFile.absolutePath}" + } + Toast.makeText(context, result, Toast.LENGTH_LONG).show() + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show() + } finally { + loadingDialog?.dismiss() } } + } - @Override - protected void onPostExecute(String s) { - if (s.equalsIgnoreCase("finished")) { - loadingView.dismiss(); - Toast.makeText(getActivity(), "Finished", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(getActivity(), "Failed", Toast.LENGTH_SHORT).show(); - } - } + override fun onDestroyView() { + super.onDestroyView() + loadingDialog?.dismiss() // Dismiss dialog to prevent leaks + _binding = null } + companion object { + fun newInstance(): ECCEGGenerateKeyFragment { + return ECCEGGenerateKeyFragment() + } + } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.kt index 5a6b589..b041788 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSADecryptFragment.kt @@ -1,199 +1,225 @@ -package id.my.berviantoleo.ecceg_rsa_app.fragment; - -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import id.my.berviantoleo.ecceg_rsa_app.R; -import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA; -import com.google.android.material.textfield.TextInputEditText; -import com.obsez.android.lib.filechooser.ChooserDialog; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.math.BigInteger; -import java.util.Objects; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cc.cloudist.acplibrary.ACProgressFlower; - -import static android.os.Environment.getExternalStorageDirectory; - - -public class RSADecryptFragment extends Fragment { - - @BindView(R.id.private_key_value) - protected TextInputEditText privateKey; - @BindView(R.id.n_value_decrypt) - protected TextInputEditText nModulus; - @BindView(R.id.file_decrypt_value) - protected TextInputEditText decryptFileLoc; - @BindView(R.id.file_decrypt_loc_value) - protected TextInputEditText decryptDestination; - @BindView(R.id.outputValueDecrypt) - protected TextInputEditText outputValue; - @BindView(R.id.InputValueDecrypt) - protected TextInputEditText inputValue; - @BindView(R.id.outputSizeValueDecrypt) - protected TextInputEditText outputSize; - @BindView(R.id.InputSizeValueDecrypt) - protected TextInputEditText inputSize; - @BindView(R.id.timeValueDecrypt) - protected TextInputEditText timeElapsedDecrypt; - private String keyPath; - private ACProgressFlower loadingView; - private long startTime; - - public RSADecryptFragment() { - // Required empty public constructor +package id.my.berviantoleo.ecceg_rsa_app.fragment + +import android.os.Bundle +import android.os.Environment +import android.text.Editable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import cc.cloudist.acplibrary.ACProgressFlower +import com.obsez.android.lib.filechooser.ChooserDialog +import id.my.berviantoleo.ecceg_rsa_app.R +import id.my.berviantoleo.ecceg_rsa_app.databinding.FragmentRsadecryptBinding +import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.math.BigInteger + +class RSADecryptFragment : Fragment() { + + private var _binding: FragmentRsadecryptBinding? = null + private val binding get() = _binding!! + + private var keyPath: String? = null + private var loadingView: ACProgressFlower? = null + private var startTime: Long = 0 + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentRsadecryptBinding.inflate(inflater, container, false) + loadingView = ACProgressFlower.Builder(context).build() + loadingView?.setCanceledOnTouchOutside(false) + loadingView?.setCancelable(false) + return binding.root } - public static RSADecryptFragment newInstance() { - return new RSADecryptFragment(); - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_rsadecrypt, container, false); - ButterKnife.bind(this, view); - loadingView = new ACProgressFlower.Builder(getContext()).build(); - loadingView.setCanceledOnTouchOutside(false); - loadingView.setCancelable(false); - return view; - } + binding.openPrivateButton.setOnClickListener { + openPrivateKeyChooser() + } - @OnClick(R.id.open_private_button) - void openPrivateKey() { - new ChooserDialog(getActivity()) - .withFilter(false, false, "pri") - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> { - keyPath = pathFile.getPath(); - loadingView.show(); - new OpenKey(RSADecryptFragment.this).execute(keyPath); - }) - .build() - .show(); + binding.selectDecryptFileButton.setOnClickListener { + openFileToDecryptChooser() + } + + binding.decryptButton.setOnClickListener { + decryptAndDisplay() + } } - @OnClick(R.id.select_decrypt_file_button) - void openFileDecrypt() { - new ChooserDialog(getActivity()) - .withStartFile(getExternalStorageDirectory().getAbsolutePath()) - .withResources(R.string.title_choose_file, R.string.title_choose, R.string.dialog_cancel) - .withChosenListener((path, pathFile) -> { - decryptFileLoc.setText(pathFile.getPath()); - inputSize.setText(String.valueOf(pathFile.length())); - loadingView.show(); - new SetInput(RSADecryptFragment.this).execute(pathFile.getPath()); - }) + private fun openPrivateKeyChooser() { + activity?.let { + ChooserDialog(it) + .withFilter(false, false, "pri") + .withStartFile(Environment.getExternalStorageDirectory().absolutePath) + .withResources( + R.string.title_choose_file, + R.string.title_choose, + R.string.dialog_cancel + ) + .withChosenListener { _, pathFile -> + keyPath = pathFile.path + loadingView?.show() + lifecycleScope.launch { + loadKeyDetails(keyPath) + } + } .build() - .show(); + .show() + } } - @OnClick(R.id.decrypt_button) - void decrypt() { - if (!Objects.requireNonNull(decryptFileLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(decryptDestination.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(nModulus.getText()).toString().equalsIgnoreCase("") - && !Objects.requireNonNull(privateKey.getText()).toString().equalsIgnoreCase("")) { - loadingView.show(); - File file = Environment.getExternalStorageDirectory(); - File location = new File(file, "RSA/"); - if (!location.exists()) { - location.mkdir(); + private suspend fun loadKeyDetails(filePath: String?) { + if (filePath == null) { + withContext(Dispatchers.Main) { + loadingView?.dismiss() + Toast.makeText(context, "Key path is null", Toast.LENGTH_SHORT).show() + } + return + } + try { + val keyContent = withContext(Dispatchers.IO) { + RSA.readKey(filePath) + } + withContext(Dispatchers.Main) { + val keyParts: Array = + keyContent.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (keyParts.size >= 2) { + binding.nValueDecrypt.setText(keyParts[0]) + binding.privateKeyValue.setText(keyParts[1]) + } else { + Toast.makeText(context, "Invalid key format", Toast.LENGTH_SHORT).show() + } + loadingView?.dismiss() + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + loadingView?.dismiss() + Toast.makeText(context, "Error loading key: ${e.message}", Toast.LENGTH_SHORT).show() + e.printStackTrace() } - new RSADecryptFragment.Decrypt(RSADecryptFragment.this).execute(decryptFileLoc.getText().toString(), - location.getAbsolutePath() + "/" + decryptDestination.getText().toString(), - nModulus.getText().toString(), - privateKey.getText().toString()); } } - private class Decrypt extends AsyncTask { - - // only retain a weak reference to the activity - Decrypt(RSADecryptFragment context) { - new WeakReference<>(context); + private fun openFileToDecryptChooser() { + activity?.let { + ChooserDialog(it) + .withStartFile(Environment.getExternalStorageDirectory().absolutePath) + .withResources( + R.string.title_choose_file, + R.string.title_choose, + R.string.dialog_cancel + ) + .withChosenListener { _, pathFile -> + binding.fileDecryptValue.setText(pathFile.path) + binding.InputSizeValueDecrypt.setText(pathFile.length().toString()) + loadingView?.show() + lifecycleScope.launch { + loadInputFileContent(pathFile.path) + } + } + .build() + .show() } + } - @Override - protected String doInBackground(String... strings) { - startTime = System.currentTimeMillis(); - RSA.decryptFile(strings[0], strings[1], new BigInteger(strings[3]), new BigInteger(strings[2])); - byte[] bytes = RSA.getBytes(strings[1]); - if (bytes != null) - return new String(bytes); - else - return ""; + private suspend fun loadInputFileContent(filePath: String?) { + if (filePath == null) { + withContext(Dispatchers.Main) { + loadingView?.dismiss() + Toast.makeText(context, "File path is null", Toast.LENGTH_SHORT).show() + } + return } - - @Override - protected void onPostExecute(String hex) { - long endTime = System.currentTimeMillis(); - timeElapsedDecrypt.setText(String.valueOf(endTime - startTime)); - outputValue.setText(hex); - File file = new File(Environment.getExternalStorageDirectory().getPath() + "/RSA/" + decryptDestination.getText().toString()); - outputSize.setText(String.valueOf(file.length())); - loadingView.dismiss(); - Toast.makeText(getActivity(), "Finished Decrypt", Toast.LENGTH_SHORT).show(); + try { + val hexContent = withContext(Dispatchers.IO) { + RSA.showHexFromFile(filePath) + } + withContext(Dispatchers.Main) { + binding.InputValueDecrypt.setText(hexContent) + loadingView?.dismiss() + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + loadingView?.dismiss() + Toast.makeText(context, "Error loading file content: ${e.message}", Toast.LENGTH_SHORT).show() + e.printStackTrace() + } } } - private class OpenKey extends AsyncTask { - - // only retain a weak reference to the activity - OpenKey(RSADecryptFragment context) { - new WeakReference<>(context); - } - - @Override - protected String doInBackground(String... strings) { - return RSA.readKey(strings[0]); - } + private fun decryptAndDisplay() { + val fileToDecryptPath = binding.fileDecryptValue.text.toString() + val destinationFileName = binding.fileDecryptLocValue.text.toString() + val nModulusStr = binding.nValueDecrypt.text.toString() + val privateKeyStr = binding.privateKeyValue.text.toString() - @Override - protected void onPostExecute(String s) { - String[] key = s.split(":"); - String N = key[0]; - String pubKey = key[1]; - privateKey.setText(pubKey); - nModulus.setText(N); - loadingView.dismiss(); + if (fileToDecryptPath.isBlank() || destinationFileName.isBlank() || nModulusStr.isBlank() || privateKeyStr.isBlank()) { + Toast.makeText(context, "Please fill all fields and select a file.", Toast.LENGTH_SHORT).show() + return } - } - private class SetInput extends AsyncTask { + loadingView?.show() - // only retain a weak reference to the activity - SetInput(RSADecryptFragment context) { - new WeakReference<>(context); + val rsaDir = File(Environment.getExternalStorageDirectory(), "RSA") + if (!rsaDir.exists()) { + rsaDir.mkdirs() } - - @Override - protected String doInBackground(String... strings) { - if (strings.length == 1) { - return RSA.showHexFromFile(strings[0]); + val destinationPath = File(rsaDir, destinationFileName).absolutePath + + lifecycleScope.launch { + try { + startTime = System.currentTimeMillis() + withContext(Dispatchers.IO) { + RSA.decryptFile( + fileToDecryptPath, + destinationPath, + BigInteger(privateKeyStr), + BigInteger(nModulusStr) + ) + } + val decryptedBytes = withContext(Dispatchers.IO) { + RSA.getBytes(destinationPath) + } + val endTime = System.currentTimeMillis() + + withContext(Dispatchers.Main) { + binding.timeValueDecrypt.setText((endTime - startTime).toString()) + binding.outputValueDecrypt.setText(decryptedBytes?.let { String(it) } ?: "") + val outFile = File(destinationPath) + binding.outputSizeValueDecrypt.setText(outFile.length().toString()) + loadingView?.dismiss() + Toast.makeText(context, "Finished Decrypt", Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + loadingView?.dismiss() + Toast.makeText(context, "Decryption failed: ${e.message}", Toast.LENGTH_SHORT) + .show() + e.printStackTrace() + } } - return ""; } + } - @Override - protected void onPostExecute(String s) { - inputValue.setText(s); - loadingView.dismiss(); - } + override fun onDestroyView() { + super.onDestroyView() + _binding = null // Clear the binding when the view is destroyed + loadingView?.dismiss() // Dismiss dialog to prevent leaks } + companion object { + fun newInstance(): RSADecryptFragment { + return RSADecryptFragment() + } + } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.kt index 023e306..dcd68ec 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAEncryptFragment.kt @@ -1,215 +1,315 @@ -package id.my.berviantoleo.ecceg_rsa_app.fragment; - - -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import id.my.berviantoleo.ecceg_rsa_app.R; -import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA; -import com.google.android.material.textfield.TextInputEditText; -import com.nareshchocha.filepickerlibrary.FilePickerResultContracts; -import com.nareshchocha.filepickerlibrary.models.DocumentFilePickerConfig; -import com.nareshchocha.filepickerlibrary.models.FilePickerResult; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.math.BigInteger; -import java.util.Objects; - -import androidx.activity.result.ActivityResultCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; - -import static android.os.Environment.getExternalStorageDirectory; - -/** - * A simple {@link Fragment} subclass. - * Use the {@link RSAEncryptFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class RSAEncryptFragment extends Fragment { - - - @BindView(R.id.public_key_value) - protected TextInputEditText publicKey; - @BindView(R.id.n_value_encrypt) - protected TextInputEditText nModulus; - @BindView(R.id.file_encrypt_value) - protected TextInputEditText encryptLoc; - @BindView(R.id.file_encrypt_loc_value) - protected TextInputEditText encryptLocValue; - @BindView(R.id.outputValue) - protected TextInputEditText outputValue; - @BindView(R.id.InputValue) - protected TextInputEditText inputValue; - @BindView(R.id.InputSizeValue) - protected TextInputEditText inputSize; - @BindView(R.id.outputSizeValue) - protected TextInputEditText outputSize; - @BindView(R.id.timeValue) - protected TextInputEditText timeValue; - private String keyPath; - private AlertDialog dialog; - private long startTime; - - public RSAEncryptFragment() { - // Required empty public constructor - } +package id.my.berviantoleo.ecceg_rsa_app.fragment + +import android.app.Activity +import android.app.AlertDialog +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.provider.OpenableColumns +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.developer.filepicker.model.DialogConfigs +import com.developer.filepicker.model.DialogProperties +import com.developer.filepicker.view.FilePickerDialog +import id.my.berviantoleo.ecceg_rsa_app.R +import id.my.berviantoleo.ecceg_rsa_app.databinding.FragmentRsaencryptBinding +import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA +import id.my.berviantoleo.ecceg_rsa_app.utils.FileUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.math.BigInteger + +class RSAEncryptFragment : Fragment() { + + private var _binding: FragmentRsaencryptBinding? = null + private val binding get() = _binding!! + + private var publicKeyPath: String? = null + private var plainTextPath: String? = null + private var publicKey: BigInteger? = null + private var n: BigInteger? = null + + private lateinit var loadingDialog: AlertDialog + + private lateinit var publicKeyFilePickerLauncher: ActivityResultLauncher + private lateinit var plainTextFilePickerLauncher: ActivityResultLauncher - public static RSAEncryptFragment newInstance() { - return new RSAEncryptFragment(); + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + publicKeyFilePickerLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { uri -> + publicKeyPath = getPathFromUri(uri) + binding.publicKeyValue.setText(publicKeyPath) + publicKeyPath?.let { + lifecycleScope.launch { + loadKeyDetails(it) + } + } + } + } + } + + plainTextFilePickerLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { uri -> + plainTextPath = getPathFromUri(uri) + binding.fileEncryptValue.setText(plainTextPath) + plainTextPath?.let { + lifecycleScope.launch { + loadInputFileContent(it) + } + } + } + } + } } - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_rsaencrypt, container, false); - ButterKnife.bind(this, view); - AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); - builder.setCancelable(false); // if you want user to wait for some process to finish, - builder.setView(R.layout.layout_loading_dialog); - dialog = builder.create(); - return view; + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentRsaencryptBinding.inflate(inflater, container, false) + return binding.root } - @OnClick(R.id.open_public_button) - void openPublicKey() { - ActivityResultLauncher launcher = registerForActivityResult(new FilePickerResultContracts.PickDocumentFile(), (ActivityResultCallback) result -> { - String errorMessage = result.getErrorMessage(); - if (errorMessage != null) { - Log.e("Picker", errorMessage); - } else { - keyPath = result.getSelectedFilePath(); - dialog.show(); - new OpenKey(RSAEncryptFragment.this).execute(keyPath); - } - }); - DocumentFilePickerConfig config = new DocumentFilePickerConfig(); - launcher.launch(config); + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupLoadingDialog() + + binding.openPublicButton.setOnClickListener { + selectPublicKeyFile() + } + + binding.selectEncryptFileButton.setOnClickListener { + selectPlainTextFile() + } + + binding.encryptButton.setOnClickListener { + encryptData() + } } - @OnClick(R.id.select_encrypt_file_button) - void openFileEncrypt() { - ActivityResultLauncher launcher = registerForActivityResult(new FilePickerResultContracts.PickDocumentFile(), (ActivityResultCallback) result -> { - String errorMessage = result.getErrorMessage(); - if (errorMessage != null) { - Log.e("Picker", errorMessage); - } else { - String path = result.getSelectedFilePath(); - if (path == null) return; - encryptLoc.setText(path); - inputSize.setText(String.valueOf(path.length())); - dialog.show(); - new SetInput(RSAEncryptFragment.this).execute(path); - } - }); - DocumentFilePickerConfig config = new DocumentFilePickerConfig(); - launcher.launch(config); + private fun setupLoadingDialog() { + loadingDialog = AlertDialog.Builder(requireContext()) + .setMessage("Processing...") + .setCancelable(false) + .create() } - @OnClick(R.id.encrypt_button) - void encrypt() { - if (!Objects.requireNonNull(encryptLoc.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(encryptLocValue.getText()).toString().equalsIgnoreCase("") && - !Objects.requireNonNull(nModulus.getText()).toString().equalsIgnoreCase("") - && !Objects.requireNonNull(publicKey.getText()).toString().equalsIgnoreCase("")) { - dialog.show(); - File file = Environment.getExternalStorageDirectory(); - File location = new File(file, "RSA/"); - if (!location.exists()) { - location.mkdir(); - } - new RSAEncryptFragment.Encrypt(RSAEncryptFragment.this).execute(encryptLoc.getText().toString(), - location.getAbsolutePath() + "/" + encryptLocValue.getText().toString(), - nModulus.getText().toString(), - publicKey.getText().toString()); + private fun selectPublicKeyFile() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "*/*" // Or a more specific MIME type if you expect certain key file types } + publicKeyFilePickerLauncher.launch(intent) } - private class Encrypt extends AsyncTask { + private fun selectPlainTextFile() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "*/*" // Allow all file types + } + plainTextFilePickerLauncher.launch(intent) + } - // only retain a weak reference to the activity - Encrypt(RSAEncryptFragment context) { - new WeakReference<>(context); + private fun getPathFromUri(uri: Uri): String? { + // This is a simplified way to get a path. For robust solution, especially for cloud files, + // you might need to copy the file to cache and use its path. + if (uri.scheme == "file") { + return uri.path + } else if (uri.scheme == "content") { + var fileName: String? = null + activity?.contentResolver?.query(uri, null, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (displayNameIndex != -1) { + fileName = cursor.getString(displayNameIndex) + } + } + } + if (fileName != null) { + val cacheDir = requireContext().cacheDir + val tempFile = File(cacheDir, fileName) + try { + val inputStream: InputStream? = requireContext().contentResolver.openInputStream(uri) + val outputStream = FileOutputStream(tempFile) + inputStream?.copyTo(outputStream) + inputStream?.close() + outputStream.close() + return tempFile.absolutePath + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(context, "Error copying file: ${e.message}", Toast.LENGTH_LONG).show() + } + } } + return null + } - @Override - protected String doInBackground(String... strings) { - startTime = System.currentTimeMillis(); - RSA.encryptedFile(strings[0], strings[1], new BigInteger(strings[3]), new BigInteger(strings[2])); - return RSA.showHexFromFile(strings[1]); + private suspend fun loadKeyDetails(filePath: String) { + withContext(Dispatchers.Main) { + loadingDialog.show() + } + try { + val keyDetails = withContext(Dispatchers.IO) { + RSA.readKey(filePath) + } + withContext(Dispatchers.Main) { + publicKey = keyDetails.first + n = keyDetails.second + binding.publicKeyValue.setText(filePath) // Show path + // binding.nValueEncrypt.setText(n.toString()) // Optionally display N if needed + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText( + context, + "Error reading public key: ${e.message}", + Toast.LENGTH_LONG + ).show() + } + } finally { + withContext(Dispatchers.Main) { + loadingDialog.dismiss() + } } + } - @Override - protected void onPostExecute(String hex) { - long endTime = System.currentTimeMillis(); - timeValue.setText(String.valueOf(endTime - startTime)); - outputValue.setText(hex); - File file = new File(Environment.getExternalStorageDirectory().getPath() + "/RSA/" + Objects.requireNonNull(encryptLocValue.getText()).toString()); - outputSize.setText(String.valueOf(file.length())); - dialog.dismiss(); - Toast.makeText(getActivity(), "Finished Encrypt", Toast.LENGTH_SHORT).show(); + private suspend fun loadInputFileContent(filePath: String) { + withContext(Dispatchers.Main) { + loadingDialog.show() + } + try { + val fileContentHex = withContext(Dispatchers.IO) { + RSA.showHexFromFile(filePath) + } + val fileSize = withContext(Dispatchers.IO) { + File(filePath).length().toString() + } + withContext(Dispatchers.Main) { + binding.inputValue.setText(fileContentHex) + binding.inputSizeValue.setText(fileSize) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText( + context, + "Error reading input file: ${e.message}", + Toast.LENGTH_LONG + ).show() + } + } finally { + withContext(Dispatchers.Main) { + loadingDialog.dismiss() + } } } - private class OpenKey extends AsyncTask { + private fun encryptData() { + val publicKeyFileLocation = binding.publicKeyValue.text.toString() + val plainTextFileLocation = binding.fileEncryptValue.text.toString() + val cipherTextSaveLocation = binding.fileEncryptLocValue.text.toString() - // only retain a weak reference to the activity - OpenKey(RSAEncryptFragment context) { - new WeakReference<>(context); + if (publicKeyFileLocation.isBlank() || plainTextFileLocation.isBlank() || cipherTextSaveLocation.isBlank()) { + Toast.makeText(context, "Please select public key, plain text file, and enter save location", Toast.LENGTH_SHORT).show() + return } - @Override - protected String doInBackground(String... strings) { - return RSA.readKey(strings[0]); + if (publicKey == null || n == null) { + Toast.makeText(context, "Public key details not loaded correctly. Please re-select the key file.", Toast.LENGTH_LONG).show() + return } - @Override - protected void onPostExecute(String s) { - String[] key = s.split(":"); - String N = key[0]; - String pubKey = key[1]; - publicKey.setText(pubKey); - nModulus.setText(N); - dialog.dismiss(); + lifecycleScope.launch { + performEncryption(plainTextFileLocation, cipherTextSaveLocation, publicKey!!, n!!) } } - private class SetInput extends AsyncTask { - - // only retain a weak reference to the activity - SetInput(RSAEncryptFragment context) { - new WeakReference<>(context); + private suspend fun performEncryption( + plainTextFilePath: String, + cipherTextSaveName: String, + keyE: BigInteger, + keyN: BigInteger + ) { + withContext(Dispatchers.Main) { + loadingDialog.show() } + var resultMessage = "Encryption Failed" + var cipherTextHex: String? = null + var outputFileSize: String? = null + var executionTime: Long? = null + var fullSavePath: String? = null - @Override - protected String doInBackground(String... strings) { - if (strings.length == 1) { - byte[] bytes = RSA.getBytes(strings[0]); - if (bytes != null) { - return new String(bytes); - } + try { + val plainBytes = withContext(Dispatchers.IO) { + RSA.getBytes(plainTextFilePath) + } + + val startTime = System.nanoTime() + val encryptedBytes = withContext(Dispatchers.IO) { + RSA.encryptBytes(plainBytes, keyE, keyN) } - return ""; + executionTime = (System.nanoTime() - startTime) / 1000000 // ms + + val newDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "RSA_ENCRYPTED") + if (!newDir.exists()) { + newDir.mkdirs() + } + fullSavePath = newDir.absolutePath + File.separator + cipherTextSaveName + + withContext(Dispatchers.IO) { + val fos = FileOutputStream(fullSavePath) + fos.write(encryptedBytes) + fos.close() + } + + cipherTextHex = withContext(Dispatchers.IO) { + RSA.showHexFromFile(fullSavePath) // Assuming this can read any file for hex display + } + outputFileSize = withContext(Dispatchers.IO) { + File(fullSavePath).length().toString() + } + resultMessage = "Encryption Successful! Saved to $fullSavePath" + + } catch (e: Exception) { + resultMessage = "Encryption Error: ${e.message}" + e.printStackTrace() + } finally { + withContext(Dispatchers.Main) { + loadingDialog.dismiss() + binding.outputValue.setText(cipherTextHex ?: "Error") + binding.outputSizeValue.setText(outputFileSize ?: "N/A") + binding.timeValue.setText(executionTime?.toString()?.plus(" ms") ?: "N/A") + Toast.makeText(context, resultMessage, Toast.LENGTH_LONG).show() + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + if (::loadingDialog.isInitialized && loadingDialog.isShowing) { + loadingDialog.dismiss() } + _binding = null + } - @Override - protected void onPostExecute(String s) { - inputValue.setText(s); - dialog.dismiss(); + companion object { + fun newInstance(): RSAEncryptFragment { + return RSAEncryptFragment() } } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt index 0f15c5b..458d18e 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/fragment/RSAGenerateKeyFragment.kt @@ -1,125 +1,145 @@ package id.my.berviantoleo.ecceg_rsa_app.fragment import android.Manifest -import android.os.AsyncTask import android.os.Bundle import android.os.Environment -import android.text.Editable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment -import butterknife.BindView -import butterknife.ButterKnife -import butterknife.OnClick -import com.google.android.material.textfield.TextInputEditText +import androidx.lifecycle.lifecycleScope import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.normal.TedPermission import id.my.berviantoleo.ecceg_rsa_app.R -import id.my.berviantoleo.ecceg_rsa_app.fragment.RSAGenerateKeyFragment +import id.my.berviantoleo.ecceg_rsa_app.databinding.FragmentRsagenerateKeyBinding +import id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File -import java.lang.ref.WeakReference -import java.util.Objects class RSAGenerateKeyFragment : Fragment() { - @JvmField - @BindView(R.id.public_location_save) - var publicLocation: TextInputEditText? = null - - @JvmField - @BindView(R.id.private_location_save) - var privateLocation: TextInputEditText? = null - - @JvmField - @BindView(R.id.byte_size) - var byteSize: TextInputEditText? = null - private var dialog: AlertDialog? = null - private var extract: PermissionListener? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } + + private var _binding: FragmentRsagenerateKeyBinding? = null + private val binding get() = _binding!! + + private var loadingDialog: AlertDialog? = null + private var permissionListener: PermissionListener? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - val view = inflater.inflate(R.layout.fragment_rsagenerate_key, container, false) - ButterKnife.bind(this, view) + ): View { + _binding = FragmentRsagenerateKeyBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupLoadingDialog() + setupPermissionListener() + + binding.generateButton.setOnClickListener { + validateAndGenerateKeys() + } + } + + private fun setupLoadingDialog() { val builder = AlertDialog.Builder(requireContext()) - builder.setCancelable(false) // if you want user to wait for some process to finish, + builder.setCancelable(false) builder.setView(R.layout.layout_loading_dialog) - dialog = builder.create() - extract = object : PermissionListener { + loadingDialog = builder.create() + } + + private fun setupPermissionListener() { + permissionListener = object : PermissionListener { override fun onPermissionGranted() { - val location = Environment.getExternalStorageDirectory() - val newLocation = File(location, "RSA/") - if (!newLocation.exists()) { - newLocation.mkdir() + val byteSizeStr = binding.byteSize.text.toString() + val privateKeyFileName = binding.privateLocationSave.text.toString() + val publicKeyFileName = binding.publicLocationSave.text.toString() + + val byteSizeInt = byteSizeStr.toIntOrNull() + + if (byteSizeInt == null) { + Toast.makeText(context, "Invalid byte size entered.", Toast.LENGTH_SHORT).show() + return + } + + lifecycleScope.launch { + generateAndSaveKeys(byteSizeInt, privateKeyFileName, publicKeyFileName) } - dialog!!.show() - id.my.berviantoleo.ecceg_rsa_app.fragment.RSAGenerateKeyFragment.GenerateKey(this@RSAGenerateKeyFragment) - .execute( - Objects.requireNonNull( - byteSize!!.text - ).toString(), - newLocation.absolutePath + "/" + privateLocation!!.text.toString(), - newLocation.absolutePath + "/" + Objects.requireNonNull( - publicLocation!!.text - ).toString() - ) } override fun onPermissionDenied(deniedPermissions: List) { - Toast.makeText(context, "Permission Denied\n$deniedPermissions", Toast.LENGTH_SHORT) - .show() + Toast.makeText(context, "Permission Denied\n$deniedPermissions", Toast.LENGTH_SHORT).show() } } - return view } - @OnClick(R.id.generate_button) - fun generateKey() { - if (!Objects.requireNonNull(byteSize!!.text).toString() - .equals("", ignoreCase = true) && !Objects.requireNonNull( - privateLocation!!.text - ).toString().equals("", ignoreCase = true) && !Objects.requireNonNull( - publicLocation!!.text - ).toString().equals("", ignoreCase = true) - ) { - if (byteSize!!.text.toString().toInt() >= 1024) { - TedPermission.create() - .setPermissionListener(extract) - .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]") - .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE) - .check() - } + private fun validateAndGenerateKeys() { + val byteSizeStr = binding.byteSize.text.toString() + val privateKeyFileName = binding.privateLocationSave.text.toString() + val publicKeyFileName = binding.publicLocationSave.text.toString() + + if (publicKeyFileName.isBlank() || privateKeyFileName.isBlank() || byteSizeStr.isBlank()) { + Toast.makeText(context, "All fields must be filled.", Toast.LENGTH_SHORT).show() + return + } + + val byteSizeInt = byteSizeStr.toIntOrNull() + if (byteSizeInt == null || byteSizeInt < 1024) { + Toast.makeText(context, "Key size must be a number and at least 1024.", Toast.LENGTH_SHORT).show() + return } + + TedPermission.create() + .setPermissionListener(permissionListener) + .setDeniedMessage("If you reject permission, you cannot use this service\n\nPlease turn on permissions at [Setting] > [Permission]") + .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE) + .check() } - private inner class GenerateKey(context: RSAGenerateKeyFragment) : - AsyncTask() { - private val activityReference = - WeakReference(context) - - override fun doInBackground(vararg strings: String): Void? { - val byteSize = strings[0].toInt() - id.my.berviantoleo.ecceg_rsa_app.lib.rsa.RSA.generateKey( - byteSize, strings[1], - strings[2] - ) - return null + private suspend fun generateAndSaveKeys(byteSize: Int, privateKeyFileName: String, publicKeyFileName: String) { + withContext(Dispatchers.Main) { + loadingDialog?.show() } - override fun onPostExecute(aVoid: Void?) { - dialog!!.dismiss() - Toast.makeText(context, "Finished", Toast.LENGTH_SHORT).show() + try { + val location = Environment.getExternalStorageDirectory() + val rsaDir = File(location, "RSA") + if (!rsaDir.exists()) { + rsaDir.mkdirs() + } + + val privateKeyPath = File(rsaDir, privateKeyFileName).absolutePath + val publicKeyPath = File(rsaDir, publicKeyFileName).absolutePath + + withContext(Dispatchers.IO) { + RSA.generateKey(byteSize, privateKeyPath, publicKeyPath) + } + + withContext(Dispatchers.Main) { + loadingDialog?.dismiss() + Toast.makeText(context, "Keys generated successfully!", Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + loadingDialog?.dismiss() + Toast.makeText(context, "Error generating keys: ${e.message}", Toast.LENGTH_LONG).show() + } + e.printStackTrace() } } + override fun onDestroyView() { + super.onDestroyView() + loadingDialog?.dismiss() // Ensure dialog is dismissed to prevent leaks + _binding = null + } + companion object { @JvmStatic fun newInstance(): RSAGenerateKeyFragment { diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt index 4f0ac7c..49038fb 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECC.kt @@ -25,7 +25,7 @@ class ECC { val m = tigaXkuadrat.add(this.a).multiply(inverseDuaY).mod(this.p) val x = m.multiply(m).subtract(BigInteger.valueOf(2).multiply(a.x)).add(this.p).mod(this.p) - val y = m.multiply(a.x?.subtract(x)).subtract(a.y).add(this.p).mod(this.p) + val y = m.multiply(a.x.subtract(x)).subtract(a.y).add(this.p).mod(this.p) val point = Point() point.x = x point.y = y diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt index 01bcfc5..c955cee 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEG.kt @@ -14,20 +14,20 @@ class ECCEG { val basePoint: Point val eCC: ECC - constructor(ECC: ECC, basePoint: Point) { - this.eCC = ECC + constructor(ecc: ECC, basePoint: Point) { + this.eCC = ecc this.basePoint = basePoint - this.privateKey = BigInteger(ECC.p.bitLength(), Random()) - .mod(ECC.p.subtract(BigInteger.ONE)) + this.privateKey = BigInteger(ecc.p.bitLength(), Random()) + .mod(ecc.p.subtract(BigInteger.ONE)) .add(BigInteger.ONE) - this.publicKey = ECC.multiply(privateKey!!, basePoint) + this.publicKey = ecc.multiply(privateKey!!, basePoint) } - constructor(ECC: ECC, basePoint: Point, privateKey: BigInteger) { - this.eCC = ECC + constructor(ecc: ECC, basePoint: Point, privateKey: BigInteger) { + this.eCC = ecc this.basePoint = basePoint this.privateKey = privateKey - this.publicKey = ECC.multiply(privateKey, basePoint) + this.publicKey = ecc.multiply(privateKey, basePoint) } fun getPrivateKey(): BigInteger { @@ -96,12 +96,17 @@ class ECCEG { .add(BigInteger.ONE) val left: Point = eCC.multiply(k, basePoint) val right: Point = eCC.add(p, eCC.multiply(k, publicKey)) - return Pair(left, right) + return Pair(left, right) // This Pair is non-null } - fun encryptBytes(bytes: ByteArray): MutableList?> { - val ret: MutableList?> = ArrayList?>() - for (aByte in bytes) ret.add(encrypt(eCC.intToPoint(BigInteger.valueOf(aByte.toLong())))) + // Changed return type here + fun encryptBytes(bytes: ByteArray): MutableList> { + // The list itself is non-null. It contains non-null Pair objects. + val ret = ArrayList>() + for (aByte in bytes) { + // encrypt() returns a non-null Pair, so we can add it directly. + ret.add(encrypt(eCC.intToPoint(BigInteger.valueOf(aByte.toLong())))) + } return ret } @@ -110,12 +115,12 @@ class ECCEG { val minusM = Point() minusM.x = m.x - minusM.y = m.y?.negate()?.mod(eCC.p) + minusM.y = m.y.negate().mod(eCC.p) return eCC.add(p.right!!, minusM) } fun decrypt(l: MutableList>): MutableList { - val ret: MutableList = ArrayList() + val ret = ArrayList() for (p in l) ret.add(decrypt(p)) return ret } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt index 4756969..abfc6c6 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/ECCEGMain.kt @@ -34,80 +34,101 @@ object ECCEGMain { val s = Scanner(System.`in`) val chosen = s.nextInt() - if (chosen == 1) { - val private_key_filepath = "key.pri" - val public_key_filepath = "key.pub" - generateKey(ecc, private_key_filepath, public_key_filepath) - } else if (chosen == 2) { - val public_key_filepath = "key.pub" - val plainfile = "datatest.txt" - val ciphfile = plainfile + ".ciph" - encryptFile(ecc, public_key_filepath, plainfile, ciphfile) - } else if (chosen == 3) { - val private_key_filepath = "key.pri" - val plainfile = "datatest.txt" - val ciphfile = "datatest.txt.ciph" - decryptFile(ecc, private_key_filepath, ciphfile, plainfile) - } else println("Wrong choice!!") + when (chosen) { + 1 -> { + val privateKeyFilepath = "key.pri" + val publicKeyFilepath = "key.pub" + generateKey(ecc, privateKeyFilepath, publicKeyFilepath) + } + 2 -> { + val publicKeyFilepath = "key.pub" + val plainfile = "datatest.txt" + val ciphfile = "$plainfile.ciph" + encryptFile(ecc, publicKeyFilepath, plainfile, ciphfile) + } + 3 -> { + val privateKeyFilepath = "key.pri" + val plainfile = "datatest.txt" // This seems to be the intended destination for the decrypted file + val ciphfile = "datatest.txt.ciph" + decryptFile(ecc, privateKeyFilepath, ciphfile, plainfile) + } + else -> println("Wrong choice!!") + } } @JvmStatic @Throws(Exception::class) - fun generateKey(ecc: ECC, private_key_filepath: String, public_key_filepath: String) { + fun generateKey(ecc: ECC, privateKeyFilepath: String, publicKeyFilepath: String) { val ecceg = ECCEG(ecc, ecc.basePoint!!) print("Private key = ") println(ecceg.getPrivateKey()) - ecceg.savePrivateKey(private_key_filepath) - println("Saved in " + private_key_filepath) + ecceg.savePrivateKey(privateKeyFilepath) + println("Saved in $privateKeyFilepath") print("Public key = ") println(ecceg.publicKey) - ecceg.savePublicKey(public_key_filepath) - println("Saved in " + public_key_filepath) + ecceg.savePublicKey(publicKeyFilepath) + println("Saved in $publicKeyFilepath") } @Throws(Exception::class) private fun encryptFile( - ecc: ECC, public_key_filepath: String, filepath_plain: String, - filepath_ciph: String? + ecc: ECC, publicKeyFilepath: String, filepathPlain: String, + filepathCiph: String? ) { val ecceg = ECCEG(ecc, ecc.basePoint!!) - ecceg.loadPublicKey(public_key_filepath) + ecceg.loadPublicKey(publicKeyFilepath) println("Public key loaded...") - val read = getBytes(filepath_plain) + val read = getBytes(filepathPlain) println("---===Plainteks===---") - println(kotlin.text.String(read!!)) + println(String(read!!)) println("---======END======---") println() + // Changed to MutableList> based on error messages val enc: MutableList> = ecceg.encryptBytes(read) println("---===Cipherteks===---") - for (pp in enc) { + for (ppPair in enc) { // ppPair is Pair (non-null Pair) + // Assuming if a pair exists, its components (left and right points) should exist. + // And if points exist, their coordinates x, y should also exist. + // If this assumption is incorrect, NullPointerExceptions can occur here. + // Proper error handling or a redesign of ECCEG.encryptBytes might be needed. print( String.format( "%02x%02x%02x%02x", - pp.left!!.x.toInt(), - pp.left!!.y.toInt(), - pp.right!!.x.toInt(), - pp.right!!.y.toInt() + ppPair.left!!.x.toInt(), + ppPair.left!!.y.toInt(), + ppPair.right!!.x.toInt(), + ppPair.right!!.y.toInt() ) ) } println() println("---======END======---") - savePointsToFile(filepath_ciph, enc) + // savePointsToFile expects MutableList> + savePointsToFile(filepathCiph, enc) } @Throws(Exception::class) private fun decryptFile( - ecc: ECC, private_key_filepath: String, - filepath_ciph: String, destination: String? + ecc: ECC, privateKeyFilepath: String, + filepathCiph: String, destinationPath: String? // Renamed for clarity, original 'destination' ) { val ecceg = ECCEG(ecc, ecc.basePoint!!) - ecceg.loadPrivateKey(private_key_filepath) + ecceg.loadPrivateKey(privateKeyFilepath) println("Private key loaded...") - val read_enc = loadPointsFromFile(filepath_ciph) - val read_dec: MutableList = ecceg.decrypt(read_enc) + // Changed to MutableList> based on error messages + val readEnc: MutableList> = loadPointsFromFile(filepathCiph) + // Align with actual signature of ecceg.decrypt + val readDec: MutableList = ecceg.decrypt(readEnc) println("---===Plainteks===---") - for (pp in read_dec) print(Char(ecc.pointToInt(pp).toByte().toUShort())) + // TODO: The decrypted content is printed to console. If it should be saved to 'destinationPath', implement file writing here. + for (pp in readDec) { + // Assuming if a point exists in the decrypted list, it should be a non-null Point. + // If pp can be null, then pp!! will throw NPE. + // If ecc.pointToInt can't handle a Point that might be intrinsically "null" (e.g. point at infinity), that's another issue. + pp?.let { + print(Char(ecc.pointToInt(it).toByte().toUShort())) + } + } println() println("---======END======---") } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt index 1c004ca..bb84335 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/ecc/Point.kt @@ -3,11 +3,11 @@ package id.my.berviantoleo.ecceg_rsa_app.lib.ecc import java.math.BigInteger class Point { - var x: BigInteger? = BigInteger.ZERO - var y: BigInteger? = BigInteger.ZERO // x = absis, y = ordinat + var x: BigInteger = BigInteger.ZERO + var y: BigInteger = BigInteger.ZERO // x = absis, y = ordinat var infinity: Boolean = false // titik O, elemen identitas override fun toString(): String { - return "($x, $y)" + return if (infinity) "Point(O)" else "($x, $y)" } } diff --git a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt index f2a55c6..0736aec 100644 --- a/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt +++ b/app/src/main/java/id/my/berviantoleo/ecceg_rsa_app/lib/rsa/RSA.kt @@ -32,7 +32,7 @@ object RSA { var i: BigInteger var pubExp = BigInteger.ONE i = BigInteger.probablePrime(bitLength / 10, rnd) - while (i.compareTo(n) < 0) { + while (i < n) { if (i.gcd(phi) == BigInteger.ONE) { pubExp = i break @@ -52,47 +52,43 @@ object RSA { if (isNull(sourceBytes)) { return "" } - return RSA.getHex(sourceBytes!!) + return getHex(sourceBytes!!) } private fun getHex(raw: ByteArray): String { val hex = StringBuilder(2 * raw.size) for (b in raw) { - hex.append(HEXES.get((b.toInt() and 0xF0) shr 4)) - .append(HEXES.get((b.toInt() and 0x0F))) + hex.append(HEXES[(b.toInt() and 0xF0) shr 4]) + .append(HEXES[b.toInt() and 0x0F]) } return hex.toString() } private fun writeKeyToFile(name: String?, n: BigInteger, d: BigInteger) { - val output = n.toString() + ":" + d.toString() + val output = "$n:$d" try { - val writer = FileOutputStream(name) - val outWriter = OutputStreamWriter(writer) - outWriter.write(output) - outWriter.close() - writer.flush() - writer.close() + FileOutputStream(name).use { fileStream -> + OutputStreamWriter(fileStream).use { outWriter -> + outWriter.write(output) + } + } } catch (e: IOException) { - Log.e("RSA", e.message!!) + Log.e("RSA", e.message ?: "Unknown error writing key to file") } } @JvmStatic fun readKey(location: String?): String { - var value: String? = ":" + var value = ":" // Default value if read fails or file is empty try { BufferedReader(FileReader(location)).use { br -> - val sCurrentLine = br.readLine() - if (sCurrentLine != null) { - value = sCurrentLine - } + value = br.readLine() ?: ":" } } catch (e: IOException) { e.printStackTrace() - return value!! + // value remains as default } - return value!! + return value } @JvmStatic @@ -103,32 +99,37 @@ object RSA { } val k = ceil(n.bitLength() / 8.0).toInt() - var c: BigInteger? - var m: BigInteger? - var EB: ByteArray? - var M: ByteArray? - val C = RSA.reshape(sourceBytes!!, k) - val out: BufferedOutputStream? + var cBigInt: BigInteger + var mBigInt: BigInteger + var ebBlock: ByteArray? + var messageBlock: ByteArray? + val ciphertextBlocks = reshape(sourceBytes!!, k) ?: return false // If reshape returns null try { - if (C != null) { - out = BufferedOutputStream(FileOutputStream(destination)) - for (aC in C) { - if (aC.size != k) return false - c = BigInteger(aC) - m = decrypt(c, d, n) - EB = toByteArray(m, k) - if (EB != null) { - M = extractData(EB) - out.write(M) + BufferedOutputStream(FileOutputStream(destination)).use { outStream -> + for (block in ciphertextBlocks) { + if (block.size != k) return false + cBigInt = BigInteger(1, block) // Ensure positive BigInteger + mBigInt = decrypt(cBigInt, d, n) + ebBlock = toByteArray(mBigInt, k) + if (ebBlock != null) { + messageBlock = extractData(ebBlock) + if (messageBlock != null) { + outStream.write(messageBlock) + } else { + // Decryption padding/format error + Log.e("RSA", "Error extracting data from decrypted block.") + return false + } + } else { + // Error converting decrypted BigInteger to byte array + Log.e("RSA", "Error converting BigInteger to byte array after decryption.") + return false } } - out.close() - } else { - return false } - out.close() } catch (e: IOException) { + Log.e("RSA", "IO Exception during decryption: ${e.message}") return false } return true @@ -136,16 +137,26 @@ object RSA { /** * Extracts the data portion of the byte array. + * EB = 00 || BT || PS || 00 || D + * BT must be 02 for decryption. */ - private fun extractData(EB: ByteArray): ByteArray? { - if (EB.size < 12 || EB[0].toInt() != 0x00 || EB[1].toInt() != 0x02) { + private fun extractData(ebBlock: ByteArray): ByteArray? { + if (ebBlock.size < 12 || ebBlock[0].toInt() != 0x00 || ebBlock[1].toInt() != 0x02) { + Log.w("RSA", "Decryption Error: EB format check failed. Size: ${ebBlock.size}, Byte0: ${ebBlock[0]}, Byte1: ${ebBlock[1]}") return null } var index = 2 - do { - } while (EB[index++].toInt() != 0x00) + // Scan for the 00 separator byte after PS + while (index < ebBlock.size && ebBlock[index].toInt() != 0x00) { + index++ + } + if (index == ebBlock.size || index < 10) { // PS must be at least 8 bytes, so 00 || BT || PS(8) || 00 implies index at least 2 + 8 + 1 = 11 + Log.w("RSA", "Decryption Error: Separator 0x00 not found after PS or PS too short.") + return null + } + index++ // Move past the 00 separator - return getSubArray(EB, index, EB.size) + return getSubArray(ebBlock, index, ebBlock.size) } /** @@ -159,7 +170,7 @@ object RSA { fun getBytes(fileName: String): ByteArray? { val fIn = File(fileName) if (!fIn.canRead()) { - System.err.println("Can't read " + fileName) + System.err.println("Can't read $fileName") return null } @@ -167,23 +178,33 @@ object RSA { try { FileInputStream(fIn).use { `in` -> val fileSize = fIn.length() - if (fileSize > Int.Companion.MAX_VALUE) { + if (fileSize > Int.MAX_VALUE) { println("Sorry, file was too large!") + return null // Return null if file is too large + } + if (fileSize == 0L) { + return ByteArray(0) // Return empty array if file is empty } bytes = ByteArray(fileSize.toInt()) var offset = 0 - var numRead = 0 - while (offset < bytes.size && (`in`.read(bytes, offset, bytes.size - offset) - .also { numRead = it }) >= 0 - ) { + var numRead: Int + while (offset < bytes.size) { + numRead = `in`.read(bytes, offset, bytes.size - offset) + if (numRead < 0) break // End of stream offset += numRead } + // Check if all bytes were read + if (offset < bytes.size) { + Log.w("RSA", "Could not read the entire file $fileName. Expected ${bytes.size}, got $offset") + // Optionally, return a truncated array or null + return bytes.copyOf(offset) // Or return null if incomplete read is an error + } } - } catch (ignored: IOException) { + } catch (_: IOException) { + // Ignored, bytes will remain null or partially filled if error occurred mid-read } - return bytes } @@ -196,82 +217,97 @@ object RSA { /** * Uses the key and returns true if encryption was successful. + * EB = 00 || BT || PS || 00 || D + * For encryption, BT = 02 */ @JvmStatic fun encryptedFile(source: String, destination: String?, e: BigInteger, n: BigInteger): Boolean { val sourceBytes = getBytes(source) if (isNull(sourceBytes)) { - System.err.println(String.format("%s contained nothing.", source)) + System.err.println("$source contained nothing.") + return false + } + if (sourceBytes!!.isEmpty()){ + System.err.println("$source is empty, nothing to encrypt.") + // Depending on desired behavior, could return true (empty file encrypted is empty file) + // or false (nothing was actually encrypted). Let's say false for now. return false } - val k = ceil(n.bitLength() / 8.0).toInt() - val BT: Byte = 0x02 - var C: ByteArray? - var M: ByteArray? - val D = RSA.reshape(sourceBytes!!, k - 11) - val EB = ByteArrayOutputStream(k) - val out: FileOutputStream? - var m: BigInteger? - var c: BigInteger? + + val k = ceil(n.bitLength() / 8.0).toInt() // Size of the RSA modulus in bytes + val btValue: Byte = 0x02 + + // Data D must be at most k - 11 bytes long + // (00 || BT || PS || 00 || D) requires 1 byte for 00, 1 for BT, at least 8 for PS, 1 for 00 separator. Total 11. + val maxDataBlockLength = k - 11 + if (maxDataBlockLength <= 0) { + System.err.println("Key size is too small for PKCS#1 v1.5 padding.") + return false + } + + val dataBlocks = reshape(sourceBytes, maxDataBlockLength) ?: return false try { - if (D != null) { - out = FileOutputStream(destination) - for (aD in D) { - EB.reset() - EB.write(0x00) - EB.write(BT.toInt()) - EB.write(makePaddingString(k - aD.size - 3)) - EB.write(0x00) - EB.write(aD) - M = EB.toByteArray() - m = BigInteger(M) - c = encrypt(m, e, n) - C = toByteArray(c, k) - out.write(C) - } + FileOutputStream(destination).use { outStream -> + for (dataBlock in dataBlocks) { + if (dataBlock.isEmpty()) continue // Should not happen with reshape logic but defensive + + val paddingStringLength = k - 3 - dataBlock.size // PS = k - mLen - 3 + val psBytes = makePaddingString(paddingStringLength) + if (psBytes == null) { + System.err.println("Failed to generate padding string. Check paddingStringLength: $paddingStringLength") + return false + } - out.close() - } else { - return false + val ebStream = ByteArrayOutputStream(k) + ebStream.write(0x00) + ebStream.write(btValue.toInt()) + ebStream.write(psBytes) + ebStream.write(0x00) + ebStream.write(dataBlock) + + val ebBlock = ebStream.toByteArray() + if (ebBlock.size != k) { + System.err.println("Encoded block size ${ebBlock.size} does not match k ($k).") + return false // Should not happen if logic is correct + } + val mBigInt = BigInteger(1, ebBlock) // Prepend 1 to ensure positive + val cBigInt = encrypt(mBigInt, e, n) + val ciphertextBlock = toByteArray(cBigInt, k) + if (ciphertextBlock != null) { + outStream.write(ciphertextBlock) + } else { + System.err.println("Failed to convert encrypted BigInteger to byte array.") + return false + } + } } } catch (ex: Exception) { - val errMsg = "An exception occured!%n%s%n%s%n%s" - System.err.println( - String.format( - errMsg, - ex.javaClass, - ex.message, - ex.getStackTrace().contentToString() - ) - ) + System.err.println("An exception occurred during encryption!\n${ex.javaClass}\n${ex.message}\n${ex.stackTraceToString()}") return false } - return true } private fun reshape(inBytes: ByteArray, colSize: Int): Array? { - var colSize = colSize - if (colSize < 1) { - colSize = 1 + var effectiveColSize = colSize + if (effectiveColSize < 1) { + effectiveColSize = 1 } - val rowSize = ceil(inBytes.size.toDouble() / colSize.toDouble()).toInt() - - if (rowSize == 0) { - return null + if (inBytes.isEmpty()) { + return emptyArray() // Or null, depending on desired contract. Empty array seems more robust. } - val outBytes: Array = arrayOfNulls(rowSize) - - for (i in 0.. (Array of Non-Nullable ByteArray) + val outBytes = Array(rowSize) { i -> + getSubArray(inBytes, i * effectiveColSize, (i + 1) * effectiveColSize)!! } return outBytes } @@ -280,58 +316,88 @@ object RSA { * Returns a portion of the array argument. */ private fun getSubArray(inBytes: ByteArray, start: Int, end: Int): ByteArray? { - var end = end + var effectiveEnd = end if (start >= inBytes.size) { - return null + // This case should ideally be prevented by reshape logic if rowSize is calculated correctly. + // If start can be >= inBytes.size, it means an empty block is requested past the end. + return ByteArray(0) // Return empty array for segments beyond input } - if (end > inBytes.size) { - end = inBytes.size + if (effectiveEnd > inBytes.size) { + effectiveEnd = inBytes.size } - val bytesToGet = end - start - if (bytesToGet < 1) { - return null + val bytesToGet = effectiveEnd - start + // bytesToGet can be 0 if start == effectiveEnd, e.g. for the last block if inBytes.size is a multiple of colSize. + // Or if start >= inBytes.size, bytesToGet can be negative if effectiveEnd remains inBytes.size. + // The check start >= inBytes.size handles cases where the start is already out of bounds. + + if (bytesToGet <= 0) { // If bytesToGet is 0 or negative (though start >= inBytes.size handles this) + return ByteArray(0) // Return empty array for zero-length or invalid segments } val outBytes = ByteArray(bytesToGet) - if (end - start >= 0) System.arraycopy(inBytes, start, outBytes, 0, end - start) - + System.arraycopy(inBytes, start, outBytes, 0, bytesToGet) return outBytes } /** * Converts a BigInteger into a byte array of the specified length. + * The BigInteger is assumed to be positive. */ private fun toByteArray(x: BigInteger, numBytes: Int): ByteArray? { - var x = x - var numBytes = numBytes - if (x.compareTo(BigInteger.valueOf(256).pow(numBytes)) >= 0) { - return null // number is to big to fit in the byte array + if (x.signum() < 0) { // BigInteger should be positive as it represents an octet string + Log.e("RSA", "Cannot convert negative BigInteger to byte array for RSA.") + return null } - - val ba = ByteArray(numBytes--) - var divAndRem: Array? - - for (power in numBytes downTo 0) { - divAndRem = x.divideAndRemainder(BigInteger.valueOf(256).pow(power)) - ba[numBytes - power] = divAndRem[0]!!.toInt().toByte() - x = divAndRem[1]!! + // x should be less than 256^numBytes + // A simple check if x is too large for numBytes: x.bitLength() > numBytes * 8 + if (x.bitLength() > numBytes * 8) { + // This case can happen if n (modulus for decryption) or c (ciphertext for encryption) + // is larger than k bytes allows, or if m (message for encryption) results in c >= 256^k + Log.e("RSA", "BigInteger $x is too large to fit in $numBytes bytes (bitLength: ${x.bitLength()}).") + return null } - return ba + val ba = x.toByteArray() // Standard BigInteger to byte array + + if (ba.size == numBytes) { + return ba + } else if (ba.size < numBytes) { + // Pad with leading zeros if shorter + val result = ByteArray(numBytes) + System.arraycopy(ba, 0, result, numBytes - ba.size, ba.size) + return result + } else { // ba.size > numBytes + // This happens if the BigInteger has a leading zero byte in its minimal representation (e.g. positive number whose MSB is 1) + // And toByteArray() adds a leading 0 byte to indicate positive. + // Or if the number is simply too big and the bitLength check wasn't sufficient (unlikely for RSA numbers if k is correct) + if (ba[0].toInt() == 0 && ba.size == numBytes + 1) { // Common case: extra leading zero byte + return ba.copyOfRange(1, ba.size) + } + Log.e("RSA", "Conversion to $numBytes bytes failed. Source array size: ${ba.size}.") + // This indicates an issue, possibly x was too large and the initial check was insufficient, + // or x.toByteArray() produced an unexpected result. + return null + } } /** - * Generates an array of pseudo-random nonzero bytes. + * Generates an array of pseudo-random nonzero bytes. Length must be at least 8. */ private fun makePaddingString(len: Int): ByteArray? { - if (len < 8) return null - val random = Random() + if (len < 8) { + Log.e("RSA", "Padding string length $len is less than minimum 8.") + return null + } + val random = SecureRandom() // Use SecureRandom for cryptographic padding - val PS = ByteArray(len) + val psBytes = ByteArray(len) for (i in 0..= 0 ) { offset += numRead @@ -115,9 +115,14 @@ object FileUtils { @JvmStatic - fun loadPointsFromFile(stringpath: String): MutableList?> { + fun loadPointsFromFile(stringpath: String): MutableList> { // Changed return type here val rawData = getBytes(stringpath) - val pair: MutableList?> = ArrayList?>() + // The list itself is non-null. It can contain nullable Pair objects. + val pair: MutableList> = ArrayList() + if (rawData == null) { // If rawData is null, return an empty list. + return pair + } + val btemp = ByteArray(4) var f = 0 var s: Int @@ -127,8 +132,9 @@ object FileUtils { var point2 = Point() point2.x = BigInteger.valueOf(1) point2.y = BigInteger.valueOf(1) - for (i in 0..<(if (rawData != null) rawData.size else 0)) { - btemp[i % 4] = rawData!![i] + // Loop only if rawData is not null. + for (i in 0 until rawData.size) { + btemp[i % 4] = rawData[i] // No need for rawData!![i] due to the null check above if (i % 4 == 3) { if ((i / 4) % 4 == 0) { f = bytesToInt(btemp) diff --git a/app/src/main/res/layout/activity_ecceg.xml b/app/src/main/res/layout/activity_ecceg.xml index 95c02c0..855415f 100644 --- a/app/src/main/res/layout/activity_ecceg.xml +++ b/app/src/main/res/layout/activity_ecceg.xml @@ -33,7 +33,7 @@ android:layout_height="wrap_content"> - - Date: Sat, 2 Aug 2025 12:17:39 +0700 Subject: [PATCH 5/5] feat: remove idea --- .gitignore | 2 + .idea/AndroidProjectSystem.xml | 6 - .idea/appInsightsSettings.xml | 40 - .idea/assetWizardSettings.xml | 148 ---- .idea/caches/build_file_checksums.ser | Bin 541 -> 0 bytes .idea/caches/deviceStreaming.xml | 1164 ------------------------- .idea/caches/gradle_models.ser | Bin 198917 -> 0 bytes .idea/codeStyles/Project.xml | 113 --- .idea/compiler.xml | 6 - .idea/deploymentTargetDropDown.xml | 10 - .idea/deploymentTargetSelector.xml | 10 - .idea/gradle.xml | 19 - .idea/jarRepositories.xml | 30 - .idea/kotlinc.xml | 6 - .idea/migrations.xml | 10 - .idea/misc.xml | 51 -- .idea/other.xml | 318 ------- .idea/runConfigurations.xml | 17 - .idea/vcs.xml | 6 - 19 files changed, 2 insertions(+), 1954 deletions(-) delete mode 100644 .idea/AndroidProjectSystem.xml delete mode 100644 .idea/appInsightsSettings.xml delete mode 100644 .idea/assetWizardSettings.xml delete mode 100644 .idea/caches/build_file_checksums.ser delete mode 100644 .idea/caches/deviceStreaming.xml delete mode 100644 .idea/caches/gradle_models.ser delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/deploymentTargetDropDown.xml delete mode 100644 .idea/deploymentTargetSelector.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/migrations.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/other.xml delete mode 100644 .idea/runConfigurations.xml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 9bf1d97..e990c38 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ /local.properties /.idea/workspace.xml /.idea/libraries +/.idea/caches +/.idea/ .DS_Store /build /captures diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml deleted file mode 100644 index 4a53bee..0000000 --- a/.idea/AndroidProjectSystem.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml deleted file mode 100644 index 26eb913..0000000 --- a/.idea/appInsightsSettings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml deleted file mode 100644 index 23485d1..0000000 --- a/.idea/assetWizardSettings.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser deleted file mode 100644 index da40dd5f8605e849beabab559759d52bf62d2106..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 541 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r}uGBYr_F>vMNC#JY1CYR(Fc`|U8WE77U+VU{N%(OP#nSB4tIg3633p6>#I&^XP(*6mb#C%gh3F~r*PF9_D - - - - - \ No newline at end of file diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser deleted file mode 100644 index 612b7809a27902a73f061fe621a43b5aa9885681..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198917 zcmdSC37lkCRX<)c(=(Gy7Lq^~5)uO06T0f{OS2A{OqeiP$P5vJ7~a}bGu2gLD9VrJ|GoFVwce|G)8|#s^q+jFsjm0l zz2}~H@A;nXp8MwSHnt82jXhp(sp)k6L9gvMN4;KW*lhcO(_9=lekW-52fc%VH);-7 zyI!-q*z_FlKpX#Fjei6GCUN!po*#6E4e`Itn;V<=HtxN&`pD&W$G^!LjM`qi?{r7Q zQRB3|2c5%CYkAb}wDz`#qX+M8+-GT3JpM@b?{X)&!C4AMjeBM8yx!?Qc%=_o@6DcW zvZtFn2Lor7HL>#RpF01yKkEGMmd2(%jk#g_$-zpW5praXeXiiC)6=IO4P6S>q-BxrLlQ;V@Idw_`4l%)En$+oH#l#2!;oG9e<_&@JFx~8Ydq)0iUPg|C>jR zEl!^i@~ApY6T{5$Y>x8`thknQJ!Z)M)qZe$;||8plR29b_M9Qu({Z|st$m{b-U#Cm zyLVfW?8Ae`LywN&CH5kl2@&Xylfu}5|83da*uJ-MujtM0Zg+y4oY8?%<5WRRyVqjB z;hln5(FVKDh}yv)i+<}YFSfg!)=in8ZwUs&cCQ=Vof~fS+i6RyH}`s@-FP{{Aa428 zy^WKWR`)x@Lu}rNRwGWFf){l%fA{TPlTkJ||AuQ$J>~1az*O1WnCr*1t@KBY$F>gi zmV%ZW3=X$3ReP;nJ#Tp_z@%;61j2e;aj_ zvIJ%CxpKliHwXP5gj07hd-Ej|Zf4BL-uJ+X1Kio~$Aj9YHD#$Ov-x`hpPHwS8oOpO z)A+>VEix(hH(b{J&8Tq>n`jG>Fy9sQgRUQRy>@`fa*DV#90p66Wy!74-_Z-%k2^<= zGjg}?3f$$zQRDpLkGY4=Ena_Yw>=Uq-Y0kQW=vtc*iqwtWk07+?ckUB5YJnPh|4s8QgpxQ zEPk5(6|v@lnH%^|8OB`Qalm3wbMcg$ZVraM<$=d;JA3@Lq=Ea6U&%am|M6=v6O*UK zJ9Rn768>kKjDAcWI4{#=bS=~21BzD*U5eKSEx|h!kBk4oS#-VA#d2n|O%TDaME#`K zq9!n1y76nq`ggjIWHOd(An|L)YV1S83--7D&Zu!ACo%WCkUDHi?&P;zG3^$%;D4z- zI>7$*Mh(H@3C-Q$V~CeeYwc#X6bD0Z(C%Z*7fri$-vNi+T6I@EV=gGPHAcBu9$Csc zxnexS*coukoU)QDEh zAaF)vtzC_E2i*sdvdtGEut`bD?3lT@AG;=z?+s}cS zXgc|Abx(KLUOa&LA!cTLVZ@q^*%Uu>xa|cw9*Jq@2kb(`Akj>)G8$m%@9ZBMLJi|w zilHKZDH=rlDx!-}7oo)re5QFOt9dc#27{P+88UVPCD_Xp(`C|KLhkYR0bPtUX40(? z*0(sFW!7`$jqzL+7&WQIWC{xIDMz!wjokI!m`lrp;QCGuJ_aT*D%V;gGF&0##wZ!* z?j^Y?5H%6sl;Lmb;N$y}E>GmG9e{~4Y6xip^K;~MI?<9AYH0Si%)*Ri%0Z|BPzsqu ziS>*ap-V318W7W|~SgPc2iH1FD2$q#zOj{6x>~NGkl@FMGk4AWM^26>n>`@`{ z*!83jp)9kXZfS!x$?t;pC8ynm7-Y67vxe~x%!*k372Lb{dS zl_(2>(V2T9TErwwe;*@2$lUaDrdI)M>7}GAp)BzW5m#cd67iP}Ji0R0oe)adM>8E} zXo;Rqx)cgKcO%4Wt?vN(qjwOSeA(Y}vqO-ay&M~PLh59%VD2BrWOO>1BeA9vT~8)~ zkn;Io;}(T%&0ov6DQxCGvov9E#5OSg9@m(n>%z#2uSY)$R>nWZ3pTnabaPycD2{#> zlOg?i>?xtu;d0*@z+u!KakWYa!~UQLLvlDy;dKySc!jYYI^nKZdmA-`1fFnPVJ>9v zOL~fUpP?shK4wZvYo<-HaY&4Z*|2U+PX=2%$}iHxrwuWj91L#k!!lMS$79V8nHx z5MIIDx|!z!{wQvZ06~m$(*4m*LYz&y>3U#7Je?-plMH2gQ^H9k)YM5$v3nxAgpNGz z#-v{%8wc>ObPneX86i$!HFV+s6Bf=d4cen1R`W$4ldk7@FXTwctqB!EE<|_S%;!Wr zXNFvB!h6M>5yErC+^D05!1EDC9a~cDJLf<=9b?smU5NThF9}qJ0n;6_*%JA6m_uV% zu)=3#Fi7##;PdC`?#>NfOuWhm6IP7f4_bB(5KuZCM3i06FeyWrl_#v?{v2toqkS|X z&Yt$fH7l4M2m?SZz#3i|Cv+G2*&NXirZkfbK*~RT11$5y00?);xXURPIG?d+$2zQj zBuB2Yh7J=dwP_ErU{;?`^L?vJZm%0i}+bYebfqzs74XIxh;*8Z*82jwEEZ~P-h?h-wAGU z5bEoW_948!w}A*}^zcn0us&*-d(m~v5yndkk@*(>M&z)y$Rouq-eqf#AN#bT2N8rl zBZ||qAoB2$t1LXq0#S%1pTD#UX*O`Kjhs=w&@K{dCmVYQG>L$&7*5;@0=nlet=jhLSq z)Tv9WnICzC?Z5bYiGIye9EF*`hcL6v=0{9rGorGYr?M$##~78-)Y!xm0Opof_xMq? z8u0mf)x(d=V+A#vPV%)V$y zh1s_UVex2STQ@iEw}&MJ?CiIr5dW<#V$Wi!`>>Eldm1~IR#{%g)mSOFHcnhZRtJwc z?_sYm;uf33{B`bh`lB2BEZ7bm>9X9~aG8-745G=9xe*hA&62F6{a7{u8u`&oWWAya|h&zBjy{pY;9tA(RzzXN?{6unYVu`39j zb+T-V_zE&h znC%o1l`yb5rTgOjf;r6IOd6Q>1~NC|Nla%*kf4)i-nOMgEg=}vc1Yvt6dcXKRLOAh$WmB28b;tX^y8D$ccHg|Y zu@%S`I_=Jl-Rs*!hHrug*K|jNRb+WNz7Nkm%cH@IWEoy?-LN4b6X#@T1-(HaoKx&K zOofv$zk?y1GiYekP;>8j;zGZe3CidpyuKaX9-HuBvS2!UO`GN#{12WS7Nv;w?-(@X z?9ihQBI%YHZMK7mm5noyP%~(pdJxM?B-~unT~1PV*hR?f6L#&{xAT#Euh}J1w9Z^+ zqdW&KY#KElGA^M zno?8couo8xj`8j&CS4*K6Zw_Sz`2~{=V@HY?c)S}3SiPpp?)SMmnT=JaH}7GZX7&=3+Dm(b!$Id(h9S4M`#U>WdQnT5 z#JNQFlu|>}}-11iUM28>$Cnw+M~0=R~S%1P@GpWC#^+`D34c z^Eu<|wt?5KGm%k|&4*mmiOGDMpU{{)6s+>qb=DIa+h7Pq@NgEg_!w8NcWP>_Ka6EKQyf?B+Z;J%XZOp-STg>sJ zkUDu1qdAJS1jk*O;mlrAM*c0Y(#4(ERMaZ+>YP8v#X_=f&T#w(z@%)fTUUtSFW)Tv-w;01^C13MR{A(xQCQ`cgQ^@S|i& zuJeKz1k<};T<5{Zax|S2B|KQ?X*m?xq6rC`E;k?avs*@ulWv1Eb`(J;!`7=#_;%w> zlcvcNX38ms*IW;+7TQl-ua2vw^{wmFb(NStUj44uJFXPWzp{ReE=c5q>(tj|QG8-D z+b1oq$FCDj6~cGD=2?~0oh{@f+-;#>p|&Qnu2CXx*K4vBny7`WeOnT+NG}Bkk zjlODGI@@^`by_V)Z&`=2R>{!g(dT-+(@NPn+eIwiDVST3o8daV#7Q!fX(Ja+S~;`Z zh{>!-G2jY8xgL|LN=W|lI`I4G1mt?msDf}jYoB!H_t-1mbnaK~x#R17_U(N3aUq;o z+XrF;VDjT7{l-`BdENsiIH$OQzv=Q%oueOSPduJCd)4bdXHAs8vsWsk%zuO0n+eww}yhWMW z5ZmiF=cUW3^xoCARh)iQRrGcM`2TE0GRrb{sH zxJtGjpI+DJ-B!xf*)Hk~iwhd>fpvMAlVmB=T;4HBgwEEohzDIEKG$bjRf*2on&-(- zkIFT$K65K8GLggYh)hl7X&_*ktGY8KriwsyniN?QQXNl~n4ae#BHw9)*tR_-ebG|MU|N`46@huH=7S_3*#`;zV?rt!5X! zm;KPFs}W|lT70hSYLuDnG|hHig*3;n=kLQ}gYC$_&S0nGWO2Qt8F4y6GR?5`b8V@mN3&M1^p>^KPWGOnw zu1^x5`)6uQa7rrV<~q!zDuFp$j1Yr7DoMEx(R4hSH1J&V zD?~3(YDcPXyfY*E33p!atRU4p-50TG3*SJ3G~e`R6!Dp&gQr_-puh8^UlA!4Il9ZJ zHu)KncR;RQa0}M@DMKT!JeG~FRIB2x_KG( zrv?!pE}nAB&cUK6#mVj!3Bb4Tgw0(8=LpM!##W$d8)?RFjyA7gFxX!dv7PNbLhFq6 z>5oaaD$jj9Z}7%n`oDZ2V~&+EVUO|9#wGoyoN>RMCoQn2E%(xA2I9A{h+Wnm-!V&e!KV>eE~73rL4@7|5G z?|jv$v01jo*m$P%=EJyhzs5P6Fr3Yb)ko0gvPi*MX|jde9PpRdjKt-2k43Ig@;*0R zxCYa={N?&LCv#JX~3s$+5UHX9-?WgbA zDaTpF6?xm(^*AwaJ6nX6wVut^-iGMb%F4D0-|&t(~nd7-pH%$iM?TVc33j zK5*6qG$Xf-UC|u3#6_WR6)y@9=m_{yv2pO#U|(bK>g9~=FIHV&5(U7 zQ}uW`Os2XC0*7OI1~#g&ZQLE~OU`Um0SfQKCNtD)Nb3Wzp>9rej#Z6m6kFA%+LWV` z`s{`@n{N)}^J3{o!Thlu!aDYmvAc%tNbC0}Ue0d55ySZM zbN~Cp95k^F;A!L5UnDp97S67JXkOG^Zn44`plx|r4F{^PRvzTabi$!9CXb)gC&gdu;nmLZT`!r&0YAN zm}kAU^GvX!f=SU$dA^SG=-76i@MH($i6BrgrzS1&!hfC?V94csz)1|7406JBhOCJ#g- zt8!4bY8D)B2S-?~jeV%5g40e$%y!zD z?9ksAEb^l-m~6!U{8N^$cMkRj(XXS%BXX_?cuT6PE?gYZ5w*ZA@9W#%pcnnXgu_4c ze%dGmkpJ=InB4d*6jmHy=`qWwC=|A%O~Lr-#HE{^BYamcBYKB80;YlQe5357y{(fN zHb(HSBY$`LwU5?ciyAF^un&`Wv$wHzX+H|Qq}^>9H8%0R@|#&f1z1>{WWLJTU;J91 zS=gKZod5V-b_-tR{kf%9zJ-^STYd7o&;FB#{N7z(5_@?0tWRN*HE^_^tDGYz0!eI% z-8Kf2P){BmtYTj{Uu#S~$wD9Bq`wbaWwUtC97^n`o1EeBNN*6=uYvk-iyZZu-KQuKZqK5(Q4hrb~U44_P#`BFo5i`afFDcyvXWpvc1!+-w1HFMo>`5o-(O}s_~WAxFgQ&D}6ObHygYfj3s`Bt{Woq5Ah_zC{9 zShd69FT64it7DUy?Qlr#Y7bEfB3mj2`U`V9n{q24;9k_+_Sl)+BqT#f;!5n=^AV5(){3k5e{*Mz;N zxmW$#wqNiyd{O*{#@-xzFAEcd;e-lB@vt-R2Zt}6KK6c25Khv-=Vux1blcExyT$v$TN@(;``nXyeDJ!Z zJLyr*>Bi+ze_2?|AgH|lLQXm*epl|%6Q&1-Io2hX1tTOWMWh|=EY3XjU=+PDk8=Jn zOQ(GD+$%SExCcI+9lYdJ=AB^nKr*uG0_+@~u`yw0M1?wY)u)){BFxKdp`cMiKiVN@ zAH(~^+lty7>mSQGdn@M97R)}-oBYbo_39sh!+lywo(`)qqO`^zLeB_JBokJ=*bHk# z;e|bZ0PpKEKTe9x1il8qH86omvv``Lge5Bq!lpUQH^@8g2v?Fmc@^G=92J8ia?x@2 zX;6!}1+O$BgqxrjqKp@nLC2fU85ea}_>mYZxmf_;zW;gePaw6Yqk8!QEW8^s-mEjFfT&rPOprX40V^E1D*x5-LyvVXmn zCk{#yrtHzL%Iq7YMs+Myjf@32gw*(bP>k^ES#d&f zlX#nN``2#>A)P8^r>{X*NC{evR5Iua;}MVIqGU=r1S_h3CvF!^7FKpBR*|N8tD?Zj zWE~Jq^HN1gDZ;!8h|3Wt*y0183D&Sx!F4}W;=x*F>l8JY{i}OW3EN#ZKSmAvsMaGE z;`PqqpqqI#$}9ZF&Cg~Vh$KhkI;~*H{P;YCH=zb3U*#rZ8}m-5f54HHN1#j=SG>QE z7jv}2e!Tz5%wm-dBTXvd7k=YnoLeXnlfboW2fgKfauZ`Cs%55rK>QHdN&FEp!Cp1| z(HY1pO$UeJ-@9b^aHC1GcY9n8jl97&2-#yH3!^9MNr+5T}KTq zKeR(TFjPx(BuA4p&+|0Pb4ywRi!&{avG@~D>pTY>;P%Zy9Jz7i045R^l)F5Fa6iP( zX@i5Z+Y@J8g2!m8DP@)wXYOx*|7#D!`CJnoLi_h(;wjU9#NBP8xxcyg8OP%bKRyM(CQJEGw22goYwpx@Jq3Bb#8n#ip>Q7aPjW zR(XuQ7rp!L|Hb$(Pe@`;ap1rpfIJukF<+IkIr^QC-1oPzsV#FKS~{cHQpCuuyph-6 z|A8ONG4SPe7?@CtRhm%S;vKbISCL)Yk!916HC=Xf*-$jc(-g~>L)mq8KL{L8wX%;w zLbK(V7n(o#qsx4ZEDkHIG~p`~N2e|%-=;Fkn0xkPP-JNQ<{Y<@i=12RW8p6^KjjxW zRu$CEwZKi%0U{w0`SDB>{0byf3PT6~Xu6`Rvg_%FrC}+DUSL?N?CYv*TDn(wzLaS{ zUtanE2AJv=sm#NPs^ufDqF%-3CV?bG1X;kPj5Y|Bj%OXk@9FM8f3 z*XKC#tAr)T+ir*YYeJBeGLYWC9Zn=Stb<3G|zvabm!65k^cc@fPn9LIrKNJkRw^)6qlUmdqfu4AoQYP}Ob2GeTX%`q2Vg z(-iR#Rt6p#8}`MQzvI=Q{{akMhqvQM!(h3;a1cflubDr<;ISxT?1~3}>z95RrwQyx zZ$k?wXIn^FfTwTz;>ibdEO;S-1=-fQEh^{(BhW+3FvC#OeH-#Z33N;KrOt^9bY5w$Dcqchky*-;=iO~bQg z-7*y)*ph7oi#k{w2nK1v?}AfU|@XKZm1*_{%(V2EdQVl1(8d)$tkeypO*5 zdX#$_Rj9R9nDT36=^(gKXJQQG84HCC4=- z0JCbzmSO3E21!}k0(hm4aZCTun|_3m@e{@4PE&4mM1Eo!r#^Pa+~)wl@iPcT@>E7X zXL_7B?4$qP;nxo2SVPfF%2^Zj$l0O+LmfyBSyd$4k`zmU-JxRr__AVXj$va)>8gba zGSQ5P=`CT&C#_VhXBXM$s2vBdCo4vUo=DDL~z%`R((QW>| zz@Urvyy}xM%f=gto5SYoD`yrx_>nm88n&Sp`7`lq$ zdA20muH+h_=gAUQst&6+FiePY7KbivhAS=OtF!-_{?9S~2WA&Qx;nW*fpL$|!Be(< z|4UHZlBv$_8(){+_c#AIH{B?f=iAAY6itc-=BHJ49TnZqn3`sKN(3Bm| z)oeGEWXX~8kCv>+FFf}5n1dy|B2ka~umekho$|@j>xFX#^DEJC;@NfAe{H{l+X?ww z(cWp96~o)(vx#x#`Fn1^{}XfUc@=>@N$SlX)r3)f>L5rFYi2@iM#-Kw0x#HTz3j@W!~=!NAI|Y8V*3j0@z`W z=&^yQ26IFAJg0+S*lLbW7J>7XS4_V-c39(1M09Ric5y6H5+31Az$<*T`2i7;BFq@M zI}sQ?dCZFrXEcjljq>3agiFPP5#g*Hws87`^u$NK7r7^^xv&jJoWB8~rt2{IvR?gY z;BYBN3>?1*XE;32@xIrTg2+i71!UOo@KuZOV~z;)39jv9?kz9sf;Yk(uv<>wcI_Fj z#e6mrG07a+O`I|T-DlWah`+!xg$Dxpj7=pm4w0|NMMGo`cw6Vb0MU_q0xh3a-ZIOJ zr-LPgO-coq%YfxI=; zP%;7tT+MQAy~Scoqs*pWkn%?rq)c@y=NOtNuazUO709sF6i?ADZlQVx^0Xw+)8R)7 zLd^-oP?j_w7F@*`6aO+hI^)?Dw67N%4`NZrQLmih$cQ?8e2Ch(jKNym{7wE5PyZ?l zX}#WCBq(EflN9CV8>iU2d-CylcP$7c{{H&jbuYU$=F&5c6ES>tBZYF5Ol`b8mtLAp{Un-y^&PM|IGUjyeM9@M6Hd$i3(A^e9RWeL>W%RSOPCfh4=NFKDr%Z_oBt+ zu4E&AjNJ_j_8JOL-~Jsm zrXLJOEIrYm&vlBB1pANsf#7OaUDJa5)KJv`nG}fs_^xVriV<3hRHnQCRs}6(x_f4f ztsE@~s^GttZHBsTNe&WYLL_9Fh6$gKiP*g=Aps6CeOq-L8T8~HP6v{cV$Z1{aDt{> zpIK8S`jO*A)dprwQQrUNabi-n_^--NS+_}*==VRm`V;rW^5B`ri%%8cK=ee_UwRX< z;x~>Lr%IvYfj@oQ-$3T`vIkLUJaXPNmykO;Xgi&SSN42Oar{v64Hsz+n(Vmv zZu-gd#o^_oDshg*)D>HARWffX)D%vvJUUgi20a^+9YuH{ip?5~sRkCJK3#{V1XAAk zyemH+OMpT(h;^SdNJB$(Y;yd7sPxw1%tRO=8ltsmDT^AM@}Lu+3t1m;0b6OJ&LAWp zN(wEc-?CD^V1^tzvP~W}PE77bYnkl$xHS$K?Bm=&Hg-QKo+Cu(NYYnM(g_Phg_Yr2 zUI=?b2_?;SEF_|Zx-46PEh#F}l8ce(*AZYMLc|QiS_xS>cu-n8HZ!<(;38pI_8r@| zO%2W@B#xS<7s$E}ATu<>G1CT$b$Si3w8RPCp|wF0K|}3EkY_#f{0tSNSX)}#JEd6V#;vhr(b#QcOlue zMC^@cO|p%ZxopfRwR9}l@W#`B<8OJQU;bvE^7PCEB%bx|XYEL`5s0S5I6YkGsoa!+ zA)!l_?Z82$>XPQ*48g?=2S791DLkHzEhiRUVY=sr;EDtct~~ismn_D!VN@Z8Cuo|P*5eJ>EL@>8sqbp2E+At^ zpKfb`B!`}_D#(s^b*nUO^<@+kza^DiLRFXLWu)LmM3o2cBh z+FJ;nuD8m%g4z#N-^MawWX03eFN+Jq$t@y08E`JZE9P&W{qU3}>RYE=q@3aB7@Vr%ngB4!Y^ zi~0nWfydi^^yn)f@+}4)J$D#9ISw(rmu;faM?R*1TRfv`!%Ip&cfZIQhZoOsoe*H6*oGdMPM{%$725?o z%Tp{Qk(Opa{T>lsrjDHN-i!Fl-Al68(qzMO48@aSO31by1Zt=$Qi$YQ6Z_*#R^r59 zB_FcSW(GG~u}b9M`r-|ZsEx~*b-)`Mi;e-_5D820<{9TL{aq{_YU2%&nDxdRB3bK) zH&lYk@a9ST><|E;K;SGXHZ-fOJM z)M!CFXP)-jAI9^B7||~D38~9Uv@s_Y$_}9H9m-DWzN{H2t%X7}D3KXrBSPSrzFiir zxQl=&V?%5eXab6WrWT?miYdFQF5`=Y;ztN>hrWrhg^#_}vZKm|X@x=Y`E*Py?h!CUMbqLy+3@L&KF`8Y6HPygDG%{-um4L=WAMnom#EYL8&8|gA1z-@P zC$&hL2o^+@yB;xRK(Tb=w{C}2&mT{^fYPdMoDDL4v-Ed=y6H#QEf?L(Lly7gXuv++ zd;RM#`IC4y5EJyHmbu!U>_p~3fJ&Yy4dtMkm=?OI>FMHxBUa_eP(m3oW33Dbe!C73 zup%3%?##KuO^AdZl)*)<<`8vnJynGp4c;o>4pHt*wiJz3)HGckTPQIpp&x^nNgJfM5UO{mTK8gj0e^rDUtjq?)bmNJw=ST)gYrn?h{3A z4T2_0ePVE@4k=|a{{g>x;Syy2gAz42960>MvW4VJHqW)-fcm>T|L~#MAQ9h;rcN(3 z#epEIl@V_b(0*{iTYeB@0x_f$cgm$`(l7@Fh6&iFY|5UFn6(zFP{%ci{e+^tL0D=X zyrx3Dm4!13V{OGmab!q{LVaM{MS`p0SO&_c*@g>y!G}{!4PBH z48Po)WdGH6RB1kY!ZYaR>7oIw-1hg6K+OChRQTZ)c~eW+;d z8NP{v&uZv|*vl58y183sRlL0pfaFPP<^N2W8abOB7P3HbQVn~_H8W(DILtt^a8?YC zgs>f7GhEau4<@yejx#4jpIbry1pR&jsc9#zN@h-l7NT}gpRA3{oFdRX|G2%qomI(H zL`hcv4W`>GKKa!1zZeS*qPuPeqibqsaLTtd2 zkj$z%HqKmel|aS522GXW!cxkxhO&n{K~_Q0cqk&`Km;)c4m2<{R0T1xfg4~5KbB>q zzMrJyoRdHf5D-U66G@rSN;m}8mnPR z9;$sSIHS*TOt;j6`JDG$qA_XyQMPx`dJ`jDARC*Fr7`)EB zDPG$|jH1#|fd#}QW-2uk5rAmglj$gv`|tU;!Bz-re&q9nJ$mz~OFL>W1rzq_^}ccb zJ8(E*c85vy!1$iMpSKDz3Me-+OTXX;sVjXSd&53_(04~tV>sd6IcJ^^@&s<`djMKQh^sl zO}#Ev6LEu>QdXOuGW>Ycrylxo;759_dfxXn79QbgRB>3iP;~rt!&jX z6`KrO_8k25Rd>c}1yK*qcK}To8hU)z5|5wTs*LkZ4a3D@djU>sRB=L_iX2-FTOv@z zwiHI*Mu3sTI?7Wt7P!d8(s4E*j@m@T9EU1-h|S=v;=s~8+ks~aI~Gwb9Y+VEz^$o{ zwNe~rPIeMqQ+dLReY7fBITcihuBqBoZD7_EfraRrs?DToXd$Zo^(jlh<^A`(@XN8Z zAiAdNQ&RyiMAuY(x+Ve)5%6X}Q5nQM_2Q@h7!dQYL|S@1#NXRp2VwZ+B-8ONV2@-!LMB;T90eQ`{uv^`b z?P5d4@#cgsQK%K@Ep~&lh81j`3Q0ug!7K>d*bFKHQ`OPy6EZ4h!K!L(BI=GaAufTZ zw?26#DSJrNz-B^F1ws+aNX&$^i6BLU^VtwphEv-NiMPQy!fkwL7dt3KFiy z!ReGkXPku7v`gbW?5Xh>{gd8&(g$mT(Zs$WJ>!%xSy4<6hhv8*fn}L`5CkY)YinL8 zNp29BrD6I%t^-DbsPX_i<3kjww{eoJDM^|lV*@+#ixpd!;qAkb9)WA3ZWi0Rj_|!V zxuJBBvkxMuxAud6n^R_+Vu2^~y5ojg^tq%gtgtS>B z&}@h*L!cd3yyXQzptK8oOmS$d9qX%duUa%a4Pf= zgFH29+OTXX0uC|AQ}gj zAVi~}LfjK`uV}PP6!OF>HAE`PMEm^8SH2C2d_f|yP|3ILEQrri|J30ZUj2T^X?_Qu zJT?DhY!1x5X77*hxGa|8#3WJP2bWEv>XqSO1G@`{5AQN}ek+y7AH4k5pW&>%3RXiGT735Wh+1BeH?j1(?>Gd0xQb##zvLgBVtC4Dd*M^0Q(^H<_mmo{6? zo~g2$C?68Wtbve?#ekyBCdO?ktf-dT-HHj!6}6(dLn03i(Y` zYDlC^l;K1NC5?zOIsWNCe900d@P&yS@APo`^I#$Rr&NOPyVnOkI%;eax8UJSVOjog zD!YI6-Jf$}*e1?B(Mv`kNRE!J5{B)VNN+WCTZK|<>wypL+P7W5G|Q;U%{f(Y3kq7% zKSE?%4*W1U!__@hN5@7EoP%mi2`Zuy;n$QqLROMIwp!{q92z) zL780spP#(z6o}Te(x2mv+K1bt)rI5>mqgJ%fI_9eM?I_Yel-;AcxN`LW>Tu$>! ze@L2%8~XO+{&N&h2BLh5+ZR$VZkX>>z>Z@0v7zqzI!dm>vn`pH>A0rrsh$y{)N0Ae z)^}CNr>Vj_rlpm9$+4}aU~?NPy`mVc*@A_!qrsEU;gOMB$0*~Gbyy5YRoMsK(x`dEz1i~f+9eiF_aX?QUVhxl$KlS zMgN03m{B06Rs2Pm0jT5WI@sZW)9+9)7PT=n6zi~1%MT^Qee5VeEf3W9Gn`@r>G*O2 zlbCBzpf7E{ntf9Ngs278A!uVVqzEBZk(vq4lsc@b#t>qJt`<=Vhvvsk|x3kvAB6XV#;vj8}EDPcOmK1WEpRHIO;7i7t=!KYbkQHKK9$+M6yhJ z8yZTJWzwHhtoX&#e*U(2iV$T>+H6M3sYp%HHPq=25MVVGPf|lK&~()Z70dNKzs#_x zl_`@_(aL;{U_lCXcC-<4`DIx_sX!D_nJ3esRZgSpH{jU9%=W3K@j2%9*7Nu;!mY=e(?Q;YZ0a`wtx=KOAU}h zYpLA7=f(F<@~Vhw48`6v!c9UBq6!C+FWb6gda`GlNOaL{2N7N`G)lL7y``3sP#A2h zNMN+*OACF)fH@#JiYtX~;Nr+^$5mWUb7c*i(-dq6Fij9yY#|+DPAC%h;7@D6RmsAs z_&`jDs728RWl0e)h#OLCF{c_gh!N0wR3-TFkIHFDjc;O6f_jux;0V=#DaeI%30E(% ziFiWvfYswrzYJIAUhtG}K5EH>R*`6P;3Kz z+;3VRDYh(q;CDY1Ya+xrUP=F%*Jh#?p6gpmh^(AY*9|0es)6G9D3oU)VW`vt_0~Gz zQbG<4m;#$4c!8|}uagDna|{q<+IFbuo~t9G5&8&h1RnOBNh%Aj6q-m!nG>c|_xQ99 zT9s^^3NXZo;M6f!lC)8oQiK@dX4rb{sm2(hHeQpg1Z3WJ!Yiy;G*Dx6Gnmtzc6Zdo z?B>%6?{3riF;PV1tI3~ylH3Gbil-k9lCg#R&$m6Au>nM|LxCA@cn$>w7?y zId&r+KPC5gf*JpC#hJf$QTz^wl7*kZkRyeMxGmj)MjvPnt2Cs0j%kL5tgEi(dmgrL z$z`6X->L%^j1JT*!bgsn7O1df3462F%tKPOqmwqBqE(x4<1{J~ideu&Vv zVriP=c_PJH$5GjE zWr1!<4$GPnu{H^D%m>~UWg%=*^su;nN$RyJCEy|+*x&)9np(3 zo446Ypy}?oK4G*7>kda(AM|v&vhHacb^0_y5_Oo*PdlV$NW$Pl(XCYMz9{ zw{^tMO-V(p+SVP}mP2MGu=9Q-8F{e7VK5rz#8j>P+#Df@hi6zAdAG0)PS$J~F_z(? zk`n5Nqk54a;51d!aZs*XL9AVeb1wgII;I@0MEf>JSK2hr@2FtcRHPy9h@ld*@fcA= z6QYlX%93hZhMc34naFFIFbQHj_FDPuc##uT5;9jRFoJ4ZWf%8acrpQFJtWBbrN#j%F{Ua{Qhj-SC(3d?0cl z+i*ru;5^RIbv)IUE#EU02LWK59f~t{9K};{@J^ZP{yP<7YN{;H($UKOA!NGcz&GS5 zI3!z9U0dY`$_1{Ws+x;nG?KKCtfeTvnR_-JP)@KB;}2P?(w1A5tePs@iDHCE%Z6h^ zQN|PF4@8Dk%Xwn_fl5yz>+k#4%Pxu6Hc>~W(oiAqiE0s*nu#)>7=IwsQ6~3achhZo zx$pP9<)xrI3frAvA@@hA+<({4FJy8*dt*-SXD_63|GkfHpAgRtqTJ6moDpVDC?lL; z`M#?fo{7`mus6;MbXN-$-*bafmrX6XpQWRf`$Ncm!;mFS)pXC6JQONbP$Jav6<>9* z6As1b5Hbj4$y0H1bcXTifMy}^%3E$#vT8l#K9QCU$A+27eIi5FP3}|aN#y=nJA==~ zYnv$dsWhyQ+^14AYq?LRqfGAK_K>g05Y*|hY@MFxbld?-7J1HawB&So^}6z3OOfDf z&%Nx4(D+NAKu>9n($YIpY*IP~7;!6)X>p+A6qvmic8apox}e(rZs!J6NJ zCr?e{!~B;NNB;MIt5Q5Qh)xRLVn)zeq3lV9CVP&d=oSttQyf{c6cgoAgH0Ia$53OP;Ovbkn5 zCdy@^qi*d4l*!@i9{scrLz<@hqdVP3I*W=&6tylbjMdpX-C~NWdmA)73p4Zg)-gO#|TpoP4Zc zPn&9CH*#rYpq}y{lhrEWNGnHR%hFLYRWooRfv=$Swkxp{m;*`EVF~D(i8LP^qM+MG zSyRQa=42l+$+U##Vn?k?rcQ+dVn)YoNZaU4D#8Xa$#gdCssvlE`AZszp5h24bA)0a%D;Z+AMY2>BBJ3`=tF4bF=Yaj zB-{3-(DPOJdOXcAY|FJ|z*C6&uqahj7Os9r9gkE>R4e@&@j$qsV)|OB=;tFL$zaEu zsjlt-JaI5)=ppSC+a4^{fEk3sQbDny;y`o4_BSgAFd;3dKyR^|7Ag=<=jT*BBI@O{ zAZ%kZsE9=`JdW>hXVrEGVxGh--ebDi;k5s^|KYI&A#O{b2|*PoL=^WkA#EZC5eqNO zhNv<#=|x}s-G>8#S_uLrD4a2qI&GkmGjd=hVJE@3b#&=7E^ncFRPx|_e0#8@|6SngB3SqU{6 z>m?pM>>$w9@?=v&E|f31iVVb&ko79Lk{fuwFB_hwTApFygp07Gg>+;&8A|mECu&Sg zVcLGHl5+eYOcIK{HHd+LOUw3m}lF{%Ms)3|2d$K8fTYK$etO8jX$_> z^`Y@>A#U)R&{Ia<$Ohsbt}i<##549kd+ekL34nq#BCt83G=ly|>xCVhbrFDoBAnuB zX|ASXr=F@SDz;6?MkpDE4l~AZV7WLp&PA#mV@_n&w$YO*&Z`HcAY94FsZc`9AgmmF ztBS0R%$y>qJilV*O)xa;v#Au&kMKgR;2Dq6XIoT|VWq9rhcS2z)9 zYDX!^udWnr9{Rm6T?@3iB0(F}4>{1}sKRB=V7WVLFHL}$C*7(2Ez)?iH*#FDnI-ZX zZjRh|`Lhx&gc!2RHlG0#%`;Wcf%{8CJY8o2b{v4Dx{@NvsxMomhR7=`mh@CZB;kxG z_7rg@HbHbZu(5l@Mm;DTi|QcO?kg&sa3RW;`^eVya6qZ6$+>6K(c~l}Q5T`|X)0C_ zb0CPcY&bR)QGuvR6B$ws1;qF-m7YZY_Zqi+JC^^%gk35P6+)lr^`KHSQPLCDEHWKs zo~JL~@&>lCx|xXg1lls5r>QdjmRo=PX@oqI=jNlk#|?;&udW`5=L2z1d7+O~KDJOM z$u&{vNkMEITbwP)GI3f3Hos}A??YcN3w6A%j{ufksmk;PT~tpJFt-o;6nI2 zn(w*+DrulhVF;P;+rFu0y-raB>9}$NueNGXXug_lQ-wdVa6`hnc&?=5tt4n;GNcFt zwN+liyV_Y5xu7ORCtgbeQ3)VC;9nl}%vb=__U#Y|sX&F=P7MM{6QO~+L!r)#Dpmi# ze&=UdBJIuuA4b7&)C`7wyw+iFA^tMo_ECO!aUQ?+mQi4EK}9B)632SuSjCnb*!$md z=BGBF4ahiW(!(4%?qYXx6XEXX{_x8Gizf~-)HSKA`5c`*W^SVVIC8g8c@IaZB0BA{ zsu(!x2uDcx5ZI-OTz^#uNOBx)O`^1wqqKz_T~GHc$-|yM?D%s-9VI;2!FaN2q8hEF zx_;dH7^(7S}nqaX{qQ2N$tCG)Ck%y>d&yc?VU$Co)OI7D_9fKEJ2~$L` z%nV;{8fX95zh8N4EPSe}@*D%+Re%^VZ(^35n}}&dcj-)N%bP%09h*=b@B**5)W^Q` zU4gs27){RofVq7f$~a`5G8{Ba+!otO&9)!F3AVJ<>oylrj<6Fnc?NTHkB?^hLDvtu z-s-gjr+;AB5dYh>xv_OmW5?3!qnzP^tC8-!w{hmu>eX@A(KGFUPqt0`;!Tcs2>T_I z2Gfi8URu3r&^rt&{Jm{=;0)4BXDqGmJJjy?$FHog(atqaebEO~p}-lyKlmuO9XT1a zp7!uZaEj;RXEa1{8PJLi}Z~5#+n)G^ z%cI-;_CS!3nG0~smCli!c)W$@a?}>Ix0z8JF^u!^>RvZ&FIF(Fa6-z=NExt6J{Wof z1S4xO@a9Z22_sk7)Z2W=GCmvUU@~qRHSQnJnhC2ko~#80vF3nzS&XH?Z)3s^qY1ma z*TK-Qa}GN_9ukZQGOwi?}&Cgf*-+RNt9?rru6Q<8Y<~?L>#OLT)jY!|JxpAKy z>3bU6mR9$#_8|dJ#u%`I1Vh=|-m$lFI({3B+93|}-s$_y&WJ9YnqJrqam{``b#H_H z%Il@(8aXWyt=Bn&_--@~k$cSwN>D*&?d*1YLN>)6oUpW-HiD*4WPHp%eG-1?w$ppi zAnw_VIfDBlDsj(VwgVf#@OJS&AoQdiE_Wx!d|BrqAY}6p8zs+)*@YS{b$D;-@w`-QP+}s?kjQVU9 zpK$NrdEop3v6hJ5ws7DqGPMc$=h$ly`A{_On+O8c(q!F~J)A9r6J}IHb~TCpq+r`_ zXt%`LNz3iUZ7n2Ua*Z;_42Yil%&0*OM#Sxsh~;8?)F9R)uG95F2N0!TVNQ%^`SZ8Ri zECCRSLH{~*^dl98Nw-mhm{U}XfW%%OHHgU}b-Eu6M~&mK5}>G!8pPb-I>XJP*eQR$ zd51tTA|ah%(eYO2hs*tbZ!nratcmSZ9L7*_+ObsILLgMuP=82~E$sNx!qC7mn%FE5 z!Pr%fh-a-dMPmzvHpOPV7{y*UYET2qwTO*ij2fM-({~Zah+tY^?V4U!L93m6)2OjA z_(4>{YVm_Z88yUNdpb2O0E7Go*j?% zzyXbDVAL9L7SaCoE#3zOq7f;ncg(l2%+f+A#nu)|eo9JJ!AjfajvB<&wPMQ|@h&ND zzz?RtQAF4zCwrzTWAlaWC`gzM4JFE)A{+hnIxiv|5m)MwQMA>zg&T$=L=~+bM>xn4 zt>&6NS6FOBhfqDDN@0$u1Jt7=k8?z`u_g%_pd$iKJpvfg9hX(Xp^j*U)EjaZ>&|%3 zeV!t)j;LsPz23lYciAZ&9cLBs&76Nv9Iy&kWFT9iDTj#HT8{794yxTE^s6fl6RDTYxE1+nvz?xdKjSaHKo4CEr5!&&_Hy5vUUDN6v_f zBCHa#6lX+F5m>jqZPeIkJR+{l8S#jND^dHI6$usaN-U*0BkD>4mRM?HMkM7SmI(H< zq9p^eMDu7ylrWe*zH5mCEiq&?BPL{__95mY9x9-g7;HWmAaSrA`18mv?gxXV_Hc;Y zV{9*E{~_6>WudIS>YA3NNjM}ikWHL~BJoqb6a!mGQ51k1trsmWcO_d^nrLSs*A{B& z;J+y3xaGgddeRIijS!S*XVfR42%f}+U7wU9Zf^PSQDXyfhPVU1K4&;u5^b6p^j-m! zM3+H*!b&lcnAKRHnmjxbmtQ+3Kf@mn0YO@t7 zz};g;R)llJux6E;hniR3-sm@>VltdY#09yMC zFU28Zs^i*w&*KiYn#Y>CpH>ft$nNvSB}|i<~TwujkWfnWpP7!**&hn4PxNc z>vf0N34=XNnaH2YjVIgEb;+_F*iEXA98Ub<>!I&xreoTUhVq=;cuFgij<i)txegIU%$a)xc^g6*kW`9ICxsJn1;21{KeCrH1i)Qb5 z`TgHo3(c0WZF|smI=Q?L0SvwbJX1VVGcC#WaS9C%YF0hlWrr7_N~j;9nZdR|FZOzi zsPPb)M$Nd@h2%>%ZzaLAlgM}$w@V_s9-h_dx+uYkcvgpq57ra)#*2jXTP}VpWY|%j4BFx zSkFD?*2~n_saI}B|Myx$< zhvn+^v2Ee+WgqWhAMYmzDx(fa1Q)lJ{W&A%gXGPiHHd+KiuL``!VY2f>xbCK->?sq zJ0~8*Q-d~M3SsWU~*3ehe}Q<+z_#jN<$uXh(UOA=NTKlPXixn z*9?P>@r>tCMARmz!^|R5c%S)^K%^dx*N5A|kv?kmW)p4@B$W*mw{hh_^>JFD;%JVo zS*jGmBzJWjNy5XWmnP;*(#S&kEeAwjC%>4a!3+vj(-)x-Q52E-EDB%Zsweecfk>gG%2U=kHd>ex)8J6N$77i2F0>$^>299th zWA2$Lv)8?88t!efUyDiFGs@X#%pa?tL&!n3@L?Vh@pelh>0_vDO z)IKZlhidi^$ti^(q5_m%*lSsYc@!cl1LV##Ktz<&)UNB8J;c{t%j_ZcnMI_#&VR?x z1R@c6?JW;Sy(NZ>nHi?y;6+_?)gVBy##B5>4ZT3qRU=d^*Z1PYn#&WrCv9aR^EHb_ z+xCnaL|+xZeJQ*gGY#S%Pd%kL^o~IdxZMl{#`(`K`ICDLY1epm0 zrHwA6U!(TL*0;F*XtTn0R758#hlLJE1Bd{MxPx{kG!&&Zu`I_-s3|d`R>(!kW62f*(zc)dX|zdUMph8d|ZD~jovt{UnV(!q6j7=6RCH7}GTHwa9eRlwm% zP3$_+==jM&Y1{d-CQl>Vbh9D8o=rCs8j6yeXw%Jvnj)BOeLC5un+Zil{t$K2*-%r% zrkf1`6_`h~>1IM(DF6~}x|tA^M?<1bHycVa&`8uzXF^8}n{GD!Gtea3bTc6!i=aCm zeCNLj1SRgD>JI?q8ML%Q*^>-S_8ddeE!mb7N0uzblyF3YW!Pqfmg)W}-oS$R%$jf8 zuO{!GT02b<2oe*o*4$rFpcBImYwo!SZriUV@1I(8@5ONw_noZ0X9ilt{Zni2tOB5j z2GN@7Cbx7~8%)Y7lMi zxV;RJRTCG125~i0x-801YT^LpWfW62{*`<>;A}=uOdBS3&H#j{ACUU1z!0JaO(CTe zMu-XvrT;vV5J8;OaRy9?GMCaV!;@n;vpJ+tlR*e_B#SB+e&;_QEl`CxpAQEff=O&U zmg6ET!|AqJ*%Ut`$HPFxmM}?QV!XjNIN~)6z{8rBp(cvj}zFKn)}{P(0s;;|?2$V{>dFLCTWm z7Rr9h8EL;t-rHKzbOt7?sgGcixU6gTUW7>E$saZ8C^`VQAp*HkctVWU*W?L@MPe!L zx?NX5B2ka3NmVHv5>rWPl9Go)qLN*gh71f6T>v#HU=VnG)&_?^qKm8MptI0-_bXrU zW??82X&D6Gs@DkykqZ)#6erh5l`I`(A%@7@*ECmg;Fh#)!_aKqm31jpW9&vmtnJtEfBC8P*^TVFkDY#6Q(HM+W7;m}7k zFlzT*fqg`qy)Id$;78OB>e7-&KVs5AZ6Y%8M+Bj|BryCtPAh~1Akp}(JM1h1{&@fU z4+sP#64U8M=xUNKMHq8{As@FTx=af$DRj;3wVQ95pr$ zUx<2MZN6|sB--ZnJFkF5YF=#JXbrsi9o z9jc}sFph-M3of;>JE6J>~9XF!RVr$_6UA<;3N2iA=hzO z2^_zu!NeN!IE!Lm`1&o+5hz9^q|**XSZ#i=+#R)-f_Wu5NGr4y*EDq>2WR<8pjeV+ zqDG@HE1K&2x)N8SkIy$n%B8T9m?C9=;&Y|GQ0cSAkbr&+tEr{kK9Glxu-jSMVb8RF> z5JprpsofUQi?}MO1Qd<1xyO;~Q&EX1a)Ic+AQQlhFQPM++-C(+5iN2mIi(;)jG$0y z$m0`HT#`G_7+b_}3AJm6O2>G{b3h_)c&83Ci$x#&aPPNj8Cb(k+i!Ol`%X6yY5T$t z#sSF*4A*dN%klyj(JIZ?LloPvl)#i_-E!lUMZ@X(gI?QTX%-q=DE`7dhq1>{A{$zT zhSx-QJwvNj-$fx!G_-0_QN*il8rjgQMM;q#M8&cm6%4tEhE~0fE8vT0Xw{;r6vK#y zRxLvEKt@#Q>QRtEGoqnYtN$8?R=s{%2`QrHP^;rCxV`_UDd62euNJOPbC25t9z!Nz8N* zdlY|}EL@Y$=94Z+V~8N{1+*QoGoqp>?1?*aXGBjC2Dd+H)Yxb|BI44Fc*K2>)U?3F z$yICe@@dmS894Yo2E*GMcQoc87aB+S=gO#Y=W(6XrEp2yS}>z`mIqF1%I$1kUe;Gh zR3c`?6$YlqcP(*qk*M#_hzVJM-T9#>K2ZQHF)CYHEwY_4#c(o>YFeQsNv6)ui4Hwi z2~^3EHC2{vFD`8?rPvxQI^Jq|Y2$V-$$#rsvV&~~WJhpH+&x>LfFhDoS2<_+{BXJ7 z?+r%tqKNd`UST051gWfAlOX49*^$9BlOzMV++S50W zl~mzepNx#9N%R-je=`grkK1bD;7By`>kmDPi^^+n`l7%^A~S+vAEO!egg8}U5h}UO z;Fcl5!c_DyFjT{KJk$t;UJ4L6*tM^QkAEx*S|`UCQ&lG-G}nqd^YwO~cCU*F6=-p)S$gzT8E zO(8dMw)_!!OF?Y{vMb^5-@5ldYOREW!KfX!Q7zdMl>yqi&vW z>49UIeqcG4iG!I`+0#rL$6-W4@5@-wd$Bz_ur;_Y2Z>v)YSX~9!Q-_~xH3reO4J^B76IRV`)z?hKw_BRTOJI11IKsz z`C@pI7OFZpi`9RD~q*K6vjksXxomK!E(V8dIQwn56 z8!9>9p;LGL{ST*PE7a^DQFj`h6fKt_1spg#20=j|5&MWg~dYr4El>$sO9 za2#L4sktgj5_lF|6F6@^FkLMSearAeBTh(8ocGL2u^^fQB4wJ3y%O(dw)LN}j}MS# zWDRnQ?uZXETK%Bnp3jvlg(qftoyA7 z83${V9BE-hqrYzVMJ0q7`>0DuQ77BFK=y*xC8o#~A|%%)glT9*tGagI6^a@$U7{{o zrTQ9CX{<|2UTq`hDbyw+qrDN+|LT&!l(^%xLbxtRRM+bcJBxjnJ){$r;U^kYW5)s% zi~8EC99o8f?0(M(ECW@EJz3SU@hmhIO)_HiOLHFcEsmX8!W@Bi|YP z5NSrGsGU_!e!cvR`UDiEJ~1XxpOg|byqTPzQJyCcCG5=hG&#fFKW*n z22YN|*(k>4Hp8^hh3t1`@X2vm$+_o{wT}AKM?gum-D>w=gh`?-u1!hNh@5-=4Dz|s z3BaxVqn%Ynz7SRN+I-==$<6KMRSUd;I(EB_NS_0y=IpV}5 z^jH+&#ApMd?;@0K`64;wK1!T%DI^gqdJq_q z2O(l6DW%_xJ$C4r%`yfw$8Jt@Xd#|OKwxPWOb)#Lt+oIYYT8+EfEtjQ_?9rMEZs15 zg;`TxVC$Y^nxP@sKX# z5u_09U~0ET*-G>fQwb=7%Ql?Mdfd4{&BP>5PYyFgmjjja{MApv8lvV;C8rc{h}t)m zhCJ#JH7s)H8R#Jvf~0oM@aGuMcn(5DT&E5*i$pix_^IglYGS2*r{9N@uJ4R;a3m^Z zsg@$SmKQ2UsDzT{I+mq7p)Si-U`vYX6}GA*jV+|#vLK?wdo?d6E8a=NBzhxsBKjn1 z^<9K0VobXh6-AtyyPTXISc{S(KZy28Jt|nBi6ZSB5v)fMMxgN zh^kyY3Nk20G#6|2&!FshtOt%~M2(@=fU}_Xo^$ViQ+@L)PX14mJ2cN!J?w1>HN}y2 z$`B=0|WXw0A0v#}^t&cMbZbXyeI#D&TA z9s&VH?Ang)M0IQ|0ue`W@|>^#GuqlKq+ahB;zojY=}rbaaAOJq{)VBp*R94Vtl+_=S2ZcwD#(eQ36(X zk}JT}Bc;d@qOYJP84S3HNKv!r3iu+X`P3t-6vT*@Xgx~uP)5|JYLbvKw}@s>Jpvf4 z9hX(X4K8v3K0YNg$!A@IlujY8app2Moj1+_$elhMG;Ef&7r{-l?FVcx^ir?eTtqdS zPSE7_^qZaFaL`ejd;H)!=diO^|@8rzmu_pkPY zy^YhCR(B3i8xAL{?(}_(p6`U;vA1z*dSN&6Js3f!uHbXi+ynQ#f*p3VdDJiy5F9$l z-ye>gQLvDHMKV7lR8+-@3okwb;*V{qS6N3pKJx#ry5H%du?MhW4sTy}2ElTF;UGqF zWY9*f9$v{`jvsTyW39XIexP{l3ijAyueS(Rco-Y98N0r_yf|DqI2@>w&LKE?{AuFx z$8J*pM?C((-e9qLz*+Lxi(iPph>DJKQ;oZQfq3rP9hbZ=>bCqj6h{y6UPf&`?Bwy= z#pADf)2pu&kDu2KM$KW^JhZaZSzsRu=u}ZWFXGOv;(=SPedj6Sf%$~h$->I$8xf<0 zAsjXFNYTWm1OMaiB>N7>t{Ynv~qdE=rM0Y8IK<GQRiw3*@_K%Mj4JvGUPdH`}bE4bBn!{up#zziD zFaIjhPVdCmJWI5bZXpF7D0ngmx+6wX^mRU59xY=DDsrS6uqCr;FCIi*9il z^8<5nI10Ls+X+NVtf6>*y_AWK^ZV@c%KkS`z3sY9eb(UE=>Zo6E8rKzQ2)?ycWm~V3Rc^4PqKug-;u_;)U-DJ%2^5V92VqG> zTx_^5IB*v~&;d1^2N&buDz5McysoJ}_H`~SAbMTQuB?H0V}OTodEx0QEsCp344?2W zb9aaLMG8ED3PvREpk@|fr5l(xUVF=JhV3sSm>V<0kE zT`D4lN+XH*yo2PHRo-*93*#_<`tLBZ#wqYgvHdz#S!mzr`qo>sxeS~sP zS#<$h8!Q09vBC{yHX1W694^oZH{6OYKmnweRgYqUk=R9*R&GX27(c#%sJ^%1X`p8a z9_$HO$6SYZ%ugq-SN8dCvVRZ+TJp)becz@b$>LhAXa0XbBco!!Zg>tkD+cV=&xXSq zObt(`@(0eF`P&8burSO`vozArk#bfAyvMT3TQo~yrL|sbW8MEU)Y7mahGAFOxdhA zpD-nR@05-6!ivkd7IDw;h2y9Bq|M`$mHLW;9XX;(>hQe$@n(}UgFImD`c|(D@plCg zX~-fL)1$N4Jt|l-1FSM8;E9$7!(}7!5YOKT)Rgo(%kfO7+A5u_oAmwzcxUWy0yXl) zqZ7u-hu8rWo=rIDI&&`M9Xe?3unq^;628@nfx+e>_BvCG%}Ay|7Y z+?-YC{Rf-RF&WvxTu*fasHUG8x8Y^*Otm4((ePu&BP*AB*$BAYaXifAjO_9w(d1XD&kRAO~3SN-*c$aj}-eK?3lSoD&l*Entr3=v7x9kgcOIC z99YC-F4I==fUc(3yqxzZR9Q%hj~1Nr&{Hn~?Pc7UCm!0zZM1^4pB^#u-~J-Uhtp)j z4tpQAjvf5;{3>jBlv@8!r~ZeJ;`G&HE$x*!u`uFi1XfA#_XN%sAWmX@NbF-C*6>3t z0t-3Ns^hc2qxN$}`;?XhK7t^X=gYDhdnRyoAoAujfR6Od!|PoIQe6v*KGll^h9vks zfnfof*=w_~hS+QTK|_6COZk%8M@4%BdyN~9l(^lXti}RUsYHC5U^)#_fh8 z6n&;T2z*|GbqU-gzy$U$V~v6RHV%CI{p5PoezRzAWWNPLQrarZYOG*QpLdZxtNN0L|>}8CXN??aesw?JrhjP^S$=djorWV+2X5i7czJYen-)ko^K;zwn8@FRmqZ z{+r2!= zQ?xg-?|>jFb(Cc_*5ZSfX!X>uiTzpSc;5|DU7sNOO#NR1CrXgEFuhQKX7>7ItReP# zF$bES9ovXHog~^D*y}|IlF}4eR%3Z@J=6$zTlr}N_F8MomaDI?BlRnyPpg%{8VTwI zUKL;h`x{t8?Cll~9Qv^yf6c)_c3reLvcHL-9K^23vKmtw-iMxJZ6a-NZTj@k$-AVw zCL^Y`6@fBhTH6wM%ZRC&Bc>LQn0xP;{y4Qy7BMwb#MF!tQyu$Kf3$r!V&6Tt$svmO zcD`(2QhLaN?0oG2_#lCNDKgM(dwV}_YEm7PF_Tdu5&)a#&BL$eiel*q?JZBsS(i$9WN{{3g z?Tzg75G18BvaH75e`*(6-RdFs#ZUgw2|sv7P8x3xW}Yhh(0CsJ&LEIeN@9DhfRiOv zzQ992oOy-LIokLn>@^-Vs3ngVP-d(4g#fqxY+^rWTjPG$q`J;S77)eX_247vJZ=H_ zC~5LAn1sEaWn!=Mts&%{`6NYLy>k-wI^S++5WdrJ_W7&7_y9d;C9z+%qIXOD9DN}NrdZ}tDA4DM_6E<#~tkEJbXa5SZ z-+t2j4lPi9jp)<5gZhX`@J#~O2{3{Eb68_wzlj4~OIwlO>+3~(Bm0dA(jd>vvKpIp zx?wxC{UKui{xijIc%-_vOY~`_qnox{f*+CQ+X6JR*Y;rzvDXfA;C#zBs;K>5(cZvb z+m9eA9gt-;mcC|rQ)JKIq;a=8_35>ShPCvW=+krnw!+^IfU9z{;%YN@CugX64Euv3b zV*Di}nWg`fCW%Ag{}^8r$B!%O8<_Ct`p9 z{_asj%#ogB>q>n{&#`3_cuUXG&7PyjJ?DPkujs8_Pv$wg$#Zn0=eXLR>wxSB6Z_oZ z-*o#%a?-8{i`YgGSR=tv1YQ-O+4kBEtTEW$!hwymQqEKR>!Q8E_S#JZrIN15vKpJ% zuO<4uZ4$Bn>BZ~AD2{3UMW41}*@rejf~5ow5?})Rp;$xgHGZfFwcU_QCuA*Gv^TQn z2NfEGpHZN!#zxh=-WqLRLF{Mky8kFGP<^WC(>6!;p-+>bVFR8nKr?&29BYWZ9_GN+ z$cOY^tOrDU1A9G)AdMW7Wi>YTK#Qr!{z+p0RCxZce=61Oe9^qT0Eui*Z(cZvb<5N#ceAc0?#y-Ay z!G-J(knMltpLX(HscvsA`n1s&W^W@w!&$nm0L|>}=J#5ACsYkRx5GP@MoRWxYwv)X zq|{NC)!1_lF7cRilGuN~wq{RIs_Vl9C)+6khfDA;1dbG-nY}(5YsmKcI1X%ibB+OY zi}nWF>v;&$AY)`%jdi-dn8%z;#QyT7q6ODUy|L)iRzqMD2^ut;2{3_uE37dP<h@it&oslw#k(cAf;8V2pqagWAJ!0i`#}yg`|!-C)PApMZ(wiVk02=> zkYzP?{^NUIMD`nr{qvWf*j`EM6GWeBn+cpKLBn$v3NV5FWUMi;FXq6igY7e@(@CPe zk$n+@q%=jA)!34U>)|K&X}gI1?%1lg3>MiU`b^tPeQcHB0Rp!PFoFH6SYu$ng9E!~ zJl>AlZx`*2>|aBWlwOx*HJ16C6CKd@9})Yb8K<{zlIl8n4t)Zyljocu&0BhoZuT5K z?m07t7wxC^$vj6ld5+FKhlQM!mQkg7vTTCk+QDlGG4j(JK{smIcvStgvw@?hB=+$>2hh}kC8n#W){FB4*A&N2?q3RSiL6lpJ&)RI~um8AKltZV;0|1VGFMu^hyD5RgQn zy$9krh|?hc#)O^Ly%HiOs+;yn<@L7WEhHzw>(5G_Dt zgUAIj9)uS}6vSg7R)g3KVyEfhAj`!50f@sOj)C|D#P31;5yUAFe+B^=681A7Am=2W zZ~Gsinmy-xVz(?GEWz92cL-c1o|0)S50d5ImPa7Hhy7`IS1C)Afj<$?zU7K%;j%Pj z_9ctmyY=!u(5rioUfp};dEkMrUD?+x=HwKY_Q>f`OIa>-%Z;xNcP;}KrgfmO z4552!We5qX3E!D|SN+bA8B{AH=>N(D8dp~JS7b;IsI#YXGzHNNL~{@@=?+MQakK)_ z8bliqKVc#_bVH~|8D#c>YAWf0eya9Tms0nr!)P<6t>a&`v+i8{_)5Tig8fG7en4a7_k zvq3xx0@fLX#s+fkKY?8;&H8r zXHXV-{b3%EYkA*5rs!fCwRk05XTQURK)g65o@_ffSYGO%YS<*iY~1Y(JsO_fKwwcn zQWExuqPJz9afSxcOi4DghGGm%@{9YPc%Fo>y1YYk-tK-@+u^jjj-uiHkP}6cf9b6Z z!X)3yj=@j2wSh$yTTvNH6fX)DN6!5r>7|hdNiL1B{w)w8;SPwya0kR>Zi{dU-#;zj z#4mo-o_h#33E#?jhh8W=K7Q9kUiPRxvi)+#(D6Btnk&zr|J>z(NzMFT1u{u?mjVv{ z30oravtRw$ss`;$x%}WfHc4VcC<_t@5f-S8(m_}idj0f>blVE?wjiPW+P#9|OjK|BEh zHg?PROvJ6uPHtB2UMVS6kdLltQKiYP?nUAB@QfK-JUiT3sWDGX2XCAeR(cyV)}z5- zAOd%gJmQioyAur;uj1+Pa7-H&^i~Ae8;jQ$0FP$>REy#)R9ZnUzc(E8d$|TAcC$&0 zUua=BksM{!gW!S|q_ew~W^~OpGN_twgBAP&Cp?n{Z^)uPe@hwTiNdi*spD4IHI~6V zn>?I{7bBv|gKl;S(#5_Zo_#U0zdYg!1uCY(|NN?M{BFRIswhjn_tb97uDD=-unot@ zN1LVKO;f6+-Katbb$+QR?6N^)I8A4%La05lUfl`I9$`Bt%-j$Gy;Rg>> z{DX^B@RCZDRg8CA;yqYi7>#7+`^(u~;%Klu-xuvXZeZ-b18?kSkKk6)eEgzcRH^TV zrmg_9pDV8z9J4pGNLcCjkDa&-oRz&rF>8&K9&DIepvV5P`*a$*-H@u1K!w+r>kot1 zam%}v24&U5eC7ODW2$18xmgulurO7yTVmqX+El}CY=KwBQS$7h{j)paNjlHqXwm`D zEX~N!1H)lYbtDu2bF(X>CpY)mcX+AoV`kuol@t$*pHUU62rG3TMjL1OgIRD>s`u8n zCO5s_)yl52D^($8Bnwk@#*-SNiuAA;deXnf|Fxp!lrWCPH6p??rY MQ45xj^HkN}0l_VHeE - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
- \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b86273d..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index 0c0c338..0000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml deleted file mode 100644 index b268ef3..0000000 --- a/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 639c779..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index e34606c..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 8d81632..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml deleted file mode 100644 index f8051a6..0000000 --- a/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 21ac703..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/other.xml b/.idea/other.xml deleted file mode 100644 index 94c96f6..0000000 --- a/.idea/other.xml +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 16660f1..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file