Repository: PatilShreyas/bytemask
Branch: main
Commit: caf7e1ec8a5a
Files: 88
Total size: 126.2 KB
Directory structure:
gitextract_2kwxtqo9/
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── publish_docs.yml
│ └── release.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── bytemask.properties
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── dev/
│ │ └── shreyaspatil/
│ │ └── bytemask/
│ │ └── example/
│ │ └── MainActivity.kt
│ └── res/
│ ├── drawable/
│ │ ├── ic_launcher_background.xml
│ │ └── ic_launcher_foreground.xml
│ ├── layout/
│ │ └── activity_main.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── xml/
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build.gradle.kts
├── bytemask-android/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── dev/
│ │ └── shreyaspatil/
│ │ └── bytemask/
│ │ └── android/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── dev/
│ │ └── shreyaspatil/
│ │ └── bytemask/
│ │ └── android/
│ │ ├── AndroidBytemask.kt
│ │ ├── impl/
│ │ │ └── AppSigningKeyAsEncryptionKeyProvider.kt
│ │ └── initializer/
│ │ └── BytemaskInitializer.kt
│ └── test/
│ └── java/
│ └── dev/
│ └── shreyaspatil/
│ └── bytemask/
│ └── android/
│ └── ExampleUnitTest.kt
├── bytemask-core/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ └── dev/
│ └── shreyaspatil/
│ └── bytemask/
│ └── core/
│ ├── Bytemask.kt
│ ├── EncryptionKeyProvider.kt
│ └── encryption/
│ ├── EncryptionSpec.kt
│ ├── EncryptionUtils.kt
│ ├── Sha256DigestableKey.kt
│ └── Value.kt
├── debug.keystore
├── docs/
│ ├── .idea/
│ │ ├── .gitignore
│ │ ├── modules.xml
│ │ └── vcs.xml
│ ├── Writerside/
│ │ ├── c.list
│ │ ├── cfg/
│ │ │ └── buildprofiles.xml
│ │ ├── in.tree
│ │ ├── redirection-rules.xml
│ │ ├── topics/
│ │ │ ├── Android-Customization.md
│ │ │ ├── Configure.md
│ │ │ ├── Declaring-properties.md
│ │ │ ├── Getting-Started.md
│ │ │ ├── Introduction.md
│ │ │ └── Read-configuration.md
│ │ ├── v.list
│ │ └── writerside.cfg
│ └── webHelpIN2-all/
│ ├── HelpTOC.json
│ ├── Map.jhm
│ ├── api-object-digest.json
│ ├── config.json
│ ├── current.help.version
│ ├── how-to.html
│ └── index.html
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle-plugin/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ └── dev/
│ └── shreyaspatil/
│ └── bytemask/
│ └── plugin/
│ ├── BytemaskPlugin.kt
│ ├── VariantSigningKeyProvider.kt
│ ├── codegen/
│ │ ├── ConfigClassGenerator.kt
│ │ └── PropertyAndValuesProvider.kt
│ ├── config/
│ │ ├── BytemaskConfig.kt
│ │ └── impl/
│ │ ├── ByteMaskVariantConfigImpl.kt
│ │ └── BytemaskConfigImpl.kt
│ ├── task/
│ │ └── BytemaskCodegenTask.kt
│ └── util/
│ └── StringExt.kt
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── spotless/
└── copyright.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Grant Permission to Execute
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build
================================================
FILE: .github/workflows/publish_docs.yml
================================================
name: Build documentation
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
id-token: write
pages: write
env:
INSTANCE: 'Writerside/in'
ARTIFACT: 'webHelpIN2-all.zip'
DOCKER_VERSION: '241.15989'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
sparse-checkout: |
.github
docs
- name: Setup docs
run: mv docs/* .
- name: Build docs using Writerside Docker builder
uses: JetBrains/writerside-github-action@v4
with:
instance: ${{ env.INSTANCE }}
artifact: ${{ env.ARTIFACT }}
docker-version: ${{ env.DOCKER_VERSION }}
- name: Save artifact with build results
uses: actions/upload-artifact@v4
with:
name: docs
path: |
artifacts/${{ env.ARTIFACT }}
artifacts/report.json
retention-days: 7
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: docs
- name: Unzip artifact
run: unzip -O UTF-8 -qq '${{ env.ARTIFACT }}' -d dir
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Package and upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: dir
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Grant Permission to Execute
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build
- name: Publish Library on Maven Central
run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
- name: Publish Gradle Plugin
run: |
echo "Publishing Gradle Plugin🚀"
./gradlew :gradle-plugin:publishPlugins
env:
GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PLUGIN_PORTAL_KEY }}
GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PLUGIN_PORTAL_SECRET }}
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/.idea/
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at shreyaspatilg@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: CONTRIBUTING.md
================================================
## Feeling Awesome! Thanks for thinking about this.
You can contribute us by filing issues, bugs and PRs. You can also take a look at active issues and fix them.
If you want to discuss on something then feel free to present your opinions, views or any other relevant comment on [discussions](https://github.com/PatilShreyas/bytemask/discussions).
### Code contribution
- Open issue regarding proposed change.
- If your proposed change is approved, Fork this repo and do changes.
- Open PR against latest *development* branch. Add nice description in PR.
- You're done!
### Code contribution checklist
- New code addition/deletion should not break existing flow of a system.
- All tests should be passed.
- Verify `./gradlew build` is passing before raising a PR.
- Reformat code with Spotless `./gradlew spotlessApply` before raising a PR.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024 Shreyas Patil
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Bytemask
Android Gradle Plugin that masks your **secret strings** for the app in the source code making it difficult to extract from _reverse engineering_.
[***▶️ Read the documentation***](https://patilshreyas.github.io/bytemask) for more information and guide to use this utility
[](https://plugins.gradle.org/plugin/dev.shreyaspatil.bytemask.plugin)
[](https://github.com/PatilShreyas/Bytemask/actions/workflows/build.yml)
[](https://github.com/PatilShreyas/Bytemask/actions/workflows/release.yml)
## 🙋♂️ Contribute
Read [contribution guidelines](CONTRIBUTING.md) for more information regarding contribution.
## 💬 Discuss?
Have any questions, doubts or want to present your opinions, views? You're always welcome. You
can [start discussions](https://github.com/PatilShreyas/bytemask/discussions).
## License
```
Copyright 2024 Shreyas Patil
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle.kts
================================================
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("dev.shreyaspatil.bytemask.plugin") version "1.0.0-alpha02"
}
android {
namespace = "dev.shreyaspatil.bytemask.example"
compileSdk = 34
defaultConfig {
applicationId = "dev.shreyaspatil.bytemask.example"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { useSupportLibrary = true }
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions { jvmTarget = "1.8" }
buildFeatures { viewBinding { enable = true } }
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
signingConfigs {
named("debug") { storeFile = rootProject.file("debug.keystore") }
create("release") { initWith(getByName("debug")) }
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-ktx:1.8.2")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
bytemaskConfig {
// UNCOMMENT THIS: To set the generated class name as "MyAppConfig"
// className.set("MyAppConfig")
// UNCOMMENT THIS: To pick secrets from the `secrets.properties`
// defaultPropertiesFileName.set("secrets.properties")
// For debug variant, enable encryption with a debug SHA-256 key.
configure("debug") { enableEncryption.set(true) }
configure("release") { enableEncryption.set(true) }
}
================================================
FILE: app/bytemask.properties
================================================
API_KEY=Hello1234
ACCESS_TOKEN=ACCESS_TOKEN_HERE
API_ENDPOINT=https://example.com
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ByteMask"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ByteMask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: app/src/main/java/dev/shreyaspatil/bytemask/example/MainActivity.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.example
import android.os.Bundle
import androidx.activity.ComponentActivity
import dev.shreyaspatil.bytemask.example.databinding.ActivityMainBinding
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
with(binding) {
buttonApiKey.setOnClickListener { setSecret(BytemaskConfig.API_KEY) }
buttonAccessToken.setOnClickListener { setSecret(BytemaskConfig.ACCESS_TOKEN) }
buttonApiEndpoint.setOnClickListener { setSecret(BytemaskConfig.API_ENDPOINT) }
}
}
private fun ActivityMainBinding.setSecret(secret: String) {
textView.text = secret
}
}
================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
================================================
FILE: app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black"
android:fontFamily="monospace"
android:gravity="center"
android:padding="16dp"
android:text="Secrets will appear here"
android:textColor="@color/white"
android:textSize="18sp" />
<Button
android:id="@+id/buttonApiKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Show API Key" />
<Button
android:id="@+id/buttonAccessToken"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Show Access Token" />
<Button
android:id="@+id/buttonApiEndpoint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Show API Endpoint" />
</LinearLayout>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">ByteMask</string>
</resources>
================================================
FILE: app/src/main/res/values/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.ByteMask" parent="android:Theme.Material.Light.NoActionBar" />
</resources>
================================================
FILE: app/src/main/res/xml/backup_rules.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
================================================
FILE: app/src/main/res/xml/data_extraction_rules.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
================================================
FILE: build.gradle.kts
================================================
plugins {
alias(libs.plugins.android.app) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.spotless) apply false
}
subprojects {
apply(plugin = rootProject.libs.plugins.spotless.get().pluginId)
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
kotlin {
target("**/*.kt")
targetExclude("$buildDir/**/*.kt")
targetExclude("bin/**/*.kt")
ktfmt().kotlinlangStyle()
licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
}
kotlinGradle {
target("**/*.gradle.kts")
ktfmt().kotlinlangStyle()
}
}
}
================================================
FILE: bytemask-android/.gitignore
================================================
/build
================================================
FILE: bytemask-android/build.gradle.kts
================================================
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.mavenPublish)
}
android {
namespace = "dev.shreyaspatil.bytemask.android"
compileSdk = 34
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions { jvmTarget = "1.8" }
}
dependencies {
api(project(":bytemask-core"))
implementation(libs.appStartup)
compileOnly(libs.annotation.jvm)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
}
================================================
FILE: bytemask-android/consumer-rules.pro
================================================
================================================
FILE: bytemask-android/gradle.properties
================================================
POM_ARTIFACT_ID=bytemask-android
POM_NAME=Bytemask - Android
POM_DESCRIPTION=Generates masked text resource.
================================================
FILE: bytemask-android/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: bytemask-android/src/androidTest/java/dev/shreyaspatil/bytemask/android/ExampleInstrumentedTest.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.android
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("dev.shreyaspatil.bytemask.android.test", appContext.packageName)
}
}
================================================
FILE: bytemask-android/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="dev.shreyaspatil.bytemask.android.initializer.BytemaskInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>
================================================
FILE: bytemask-android/src/main/java/dev/shreyaspatil/bytemask/android/AndroidBytemask.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.android
import android.content.Context
import dev.shreyaspatil.bytemask.android.impl.AppSigningKeyAsEncryptionKeyProvider
import dev.shreyaspatil.bytemask.core.Bytemask
/** A helper class for initializing Bytemask in android environment */
object AndroidBytemask {
/** Initializes Bytemask */
@JvmStatic
fun init(context: Context) {
Bytemask.init(AppSigningKeyAsEncryptionKeyProvider(context))
}
}
================================================
FILE: bytemask-android/src/main/java/dev/shreyaspatil/bytemask/android/impl/AppSigningKeyAsEncryptionKeyProvider.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.android.impl
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.os.Build
import androidx.annotation.RequiresApi
import dev.shreyaspatil.bytemask.core.EncryptionKeyProvider
import dev.shreyaspatil.bytemask.core.encryption.Sha256DigestableKey
import java.security.MessageDigest
/**
* A class that provides the SHA-256 digest of the app's signing key.
*
* This class implements the AppSigningKeyInfoProvider interface.
*
* @property context The application context.
*/
internal class AppSigningKeyAsEncryptionKeyProvider(private val context: Context) :
EncryptionKeyProvider {
private val sha256 =
Sha256DigestableKey(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getSha256SignatureApi28Impl()
} else {
getSha256SignaturePreApi28Impl()
}
.uppercase()
)
/**
* Returns the SHA-256 digest of the app's signing key.
*
* This function checks the Android version and uses the appropriate method to retrieve the
* app's signing key. The signing key is then hashed using SHA-256 and returned as a
* Sha256DigestableKey.
*
* @return The SHA-256 digest of the app's signing key as a Sha256DigestableKey.
*/
override fun get(): Sha256DigestableKey {
return sha256
}
/**
* Retrieves the SHA-256 digest of the app's signing key for Android versions below API 28.
*
* @return The SHA-256 digest of the app's signing key as a string.
*/
@Suppress("DEPRECATION")
private fun getSha256SignaturePreApi28Impl(): String {
val packageInfo =
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNATURES
)
val signatures = packageInfo.signatures
return retrieveSha256(signatures)
}
/**
* Retrieves the SHA-256 digest of the app's signing key for Android versions API 28 and above.
*
* This function uses the GET_SIGNING_CERTIFICATES flag, which is available from API 28.
*
* @return The SHA-256 digest of the app's signing key as a string.
*/
@RequiresApi(Build.VERSION_CODES.P)
private fun getSha256SignatureApi28Impl(): String {
val packageInfo =
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNING_CERTIFICATES
)
val signatures = packageInfo.signingInfo.apkContentsSigners
return retrieveSha256(signatures)
}
/**
* Retrieves the SHA-256 digest of the given signatures.
*
* This function hashes each signature using SHA-256 and concatenates the results into a string.
*
* @param signatures The signatures to hash.
* @return The SHA-256 digest of the signatures as a string.
*/
private fun retrieveSha256(signatures: Array<Signature>): String {
val md = MessageDigest.getInstance("SHA-256")
for (signature in signatures) {
md.update(signature.toByteArray())
}
val digest = md.digest()
val hexString = StringBuilder()
for (byte in digest) {
hexString.append(String.format("%02X", byte))
}
return hexString.toString()
}
}
================================================
FILE: bytemask-android/src/main/java/dev/shreyaspatil/bytemask/android/initializer/BytemaskInitializer.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.android.initializer
import android.content.Context
import androidx.startup.Initializer
import dev.shreyaspatil.bytemask.android.AndroidBytemask
import dev.shreyaspatil.bytemask.core.Bytemask
/** Initializes ByteMask at the application startup */
class BytemaskInitializer : Initializer<Bytemask> {
override fun create(context: Context): Bytemask {
AndroidBytemask.init(context)
return Bytemask.getInstance()
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
================================================
FILE: bytemask-android/src/test/java/dev/shreyaspatil/bytemask/android/ExampleUnitTest.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.android
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
================================================
FILE: bytemask-core/.gitignore
================================================
/build
================================================
FILE: bytemask-core/build.gradle.kts
================================================
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.mavenPublish)
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
================================================
FILE: bytemask-core/gradle.properties
================================================
POM_ARTIFACT_ID=bytemask-core
POM_NAME=Bytemask - Core
POM_DESCRIPTION=Generates masked text resource.
================================================
FILE: bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/Bytemask.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.core
import dev.shreyaspatil.bytemask.core.encryption.EncryptionSpec
import dev.shreyaspatil.bytemask.core.encryption.asEncryptedValue
import dev.shreyaspatil.bytemask.core.encryption.asPlainValue
import dev.shreyaspatil.bytemask.core.encryption.decrypt
import dev.shreyaspatil.bytemask.core.encryption.encrypt
/**
* ByteMask is a class that provides functionality for masking and unmasking strings. It uses an
* [EncryptionKeyProvider] to get the key for encryption and decryption.
*
* @property encryptionKeyProvider used to get the key for encryption and decryption.
*/
class Bytemask private constructor(private val encryptionKeyProvider: EncryptionKeyProvider) {
/**
* Masks a string by encrypting it.
*
* @param encryptionSpec The details of the encryption, such as the algorithm and mode.
* @param value The string to be masked.
* @return The masked string.
*/
fun mask(encryptionSpec: EncryptionSpec, value: String): String {
return value
.asPlainValue()
.encrypt(detail = encryptionSpec, key = encryptionKeyProvider.get())
.value
}
/**
* Unmasks a string by decrypting it.
*
* @param encryptionSpec The details of the encryption, such as the algorithm and mode.
* @param value The string to be unmasked.
* @return The unmasked string.
*/
fun unmask(encryptionSpec: EncryptionSpec, value: String): String {
return value
.asEncryptedValue()
.decrypt(detail = encryptionSpec, key = encryptionKeyProvider.get())
.value
}
companion object {
@Volatile private var INSTANCE: Bytemask? = null
/**
* Initializes the ByteMask.
*
* @param encryptionKeyProvider An instance of [EncryptionKeyProvider] used to get the key
* for encryption and decryption.
*/
fun init(encryptionKeyProvider: EncryptionKeyProvider) {
if (INSTANCE != null) return
synchronized(this) {
if (INSTANCE == null) {
val byteMask = Bytemask(encryptionKeyProvider = encryptionKeyProvider)
INSTANCE = byteMask
}
}
}
/**
* Returns the instance of ByteMask.
*
* @return The singleton instance of ByteMask.
* @throws IllegalStateException If the ByteMask instance has not been initialized.
*/
fun getInstance(): Bytemask {
return INSTANCE
?: synchronized(this) {
INSTANCE ?: throw IllegalStateException("ByteMask is not initialized.")
}
}
}
}
================================================
FILE: bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/EncryptionKeyProvider.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.core
import dev.shreyaspatil.bytemask.core.encryption.Sha256DigestableKey
/** Provides information about the App Signing Key. */
interface EncryptionKeyProvider {
/** Returns the SHA-256 from the app signing information. */
fun get(): Sha256DigestableKey
}
================================================
FILE: bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/EncryptionSpec.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.core.encryption
import java.io.Serializable
class EncryptionSpec(val algorithm: String, val transformation: String) : Serializable
================================================
FILE: bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/EncryptionUtils.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.core.encryption
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@OptIn(ExperimentalEncodingApi::class)
/** This object provides utility functions for AES encryption and decryption. */
object EncryptionUtils {
/**
* Encrypts a string using AES encryption.
*
* @param key The encryption key as a string. It should be hashed with SHA-256.
* @param value The string to encrypt.
* @return The encrypted string.
*/
fun encrypt(
detail: EncryptionSpec,
key: Sha256DigestableKey,
plain: Value.Plain
): Value.Encrypted {
// Generate a random Initialization Vector (IV)
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
// Generate a SecretKeySpec from the SHA-256 hashed key
val secretKeySpec = SecretKeySpec(key.digest(), detail.algorithm)
// Create an IvParameterSpec from the IV
val ivParameterSpec = IvParameterSpec(iv)
// Get an instance of the Cipher
val cipher = Cipher.getInstance(detail.transformation)
// Initialize the Cipher in ENCRYPT_MODE with the SecretKeySpec and IvParameterSpec
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
// Encrypt the value and return it as a Base64 encoded string
val encryptedValue = cipher.doFinal(plain.value.toByteArray())
return Base64.Default.encode(iv + encryptedValue).asEncryptedValue()
}
/**
* Decrypts an AES encrypted string.
*
* @param key The encryption key as a string. It should be hashed with SHA-256.
* @param encrypted The encrypted string to decrypt.
* @return The decrypted string.
*/
fun decrypt(
detail: EncryptionSpec,
key: Sha256DigestableKey,
encrypted: Value.Encrypted
): Value.Plain {
// Decode the encrypted string from Base64 to ByteArray
val decoded = Base64.Default.decode(encrypted.value)
// Extract the Initialization Vector (IV) from the decoded ByteArray
val iv = decoded.copyOfRange(0, 16)
// Extract the encrypted value from the decoded ByteArray
val encryptedValue = decoded.copyOfRange(16, decoded.size)
// Generate a SecretKeySpec from the SHA-256 hashed key
val secretKeySpec = SecretKeySpec(key.digest(), detail.algorithm)
// Create an IvParameterSpec from the IV
val ivParameterSpec = IvParameterSpec(iv)
// Get an instance of the Cipher
val cipher = Cipher.getInstance(detail.transformation)
// Initialize the Cipher in DECRYPT_MODE with the SecretKeySpec and IvParameterSpec
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
// Decrypt the encrypted value and return it as a string
return cipher.doFinal(encryptedValue).toString(Charsets.UTF_8).asPlainValue()
}
}
/**
* Encrypts the value.
*
* This function uses the EncryptionUtils.encrypt function to encrypt the value.
*
* @param key The encryption key as a Sha256DigestableKey.
* @return The encrypted value as a Value.Encrypted.
*/
inline fun Value.Plain.encrypt(detail: EncryptionSpec, key: Sha256DigestableKey) =
EncryptionUtils.encrypt(detail, key, this)
/**
* Decrypts the value.
*
* This function uses the EncryptionUtils.decrypt function to decrypt the value.
*
* @param key The encryption key as a Sha256DigestableKey.
* @return The decrypted value as a Value.Plain.
*/
inline fun Value.Encrypted.decrypt(detail: EncryptionSpec, key: Sha256DigestableKey) =
EncryptionUtils.decrypt(detail, key, this)
================================================
FILE: bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/Sha256DigestableKey.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.core.encryption
import java.io.Serializable
import java.security.MessageDigest
/**
* A value class that represents a key that can be digested using SHA-256.
*
* @property value The string value of the key.
*/
@JvmInline
value class Sha256DigestableKey(private val value: String) : Serializable {
/**
* Digests the key using SHA-256.
*
* @return The SHA-256 hash of the key as a ByteArray.
*/
fun digest(): ByteArray {
// Get an instance of the MessageDigest with SHA-256
val digest = MessageDigest.getInstance("SHA-256")
// Return the SHA-256 hash of the string
return digest.digest(value.toByteArray(Charsets.UTF_8))
}
}
================================================
FILE: bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/Value.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.core.encryption
/**
* A interface for a value that can be either plain or encrypted.
*
* @property value The string value of the Value.
*/
sealed interface Value {
val value: String
/**
* A data class representing a plain (unencrypted) value.
*
* @property value The string value of the plain value.
*/
data class Plain(override val value: String) : Value
/**
* A data class representing an encrypted value.
*
* @property value The string value of the encrypted value.
*/
data class Encrypted(override val value: String) : Value
}
/**
* Converts a String to a Plain Value.
*
* @return A Plain Value with this string as the value.
*/
fun String.asPlainValue() = Value.Plain(this)
/**
* Converts a String to an Encrypted Value.
*
* @return An Encrypted Value with this string as the value.
*/
fun String.asEncryptedValue() = Value.Encrypted(this)
================================================
FILE: docs/.idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
================================================
FILE: docs/.idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/docs.iml" filepath="$PROJECT_DIR$/.idea/docs.iml" />
</modules>
</component>
</project>
================================================
FILE: docs/.idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
================================================
FILE: docs/Writerside/c.list
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE categories
SYSTEM "https://resources.jetbrains.com/writerside/1.0/categories.dtd">
<categories>
<category id="wrs" name="Writerside documentation" order="1"/>
</categories>
================================================
FILE: docs/Writerside/cfg/buildprofiles.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<buildprofiles xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/build-profiles.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<variables>
<download-page>https://github.com/PatilShreyas/bytemask</download-page>
<download-title>GitHub</download-title>
<showDownloadButton>true</showDownloadButton>
<enable-browser-edits>true</enable-browser-edits>
<browser-edits-url>https://github.com/PatilShreyas/bytemask/tree/main/docs/Writerside/</browser-edits-url>
<custom-favicons>https://raw.githubusercontent.com/PatilShreyas/bytemask/main/icon.png</custom-favicons>
<header-logo>https://raw.githubusercontent.com/PatilShreyas/bytemask/main/icon.png</header-logo>
</variables>
<build-profile instance="in">
<variables>
<noindex-content>false</noindex-content>
<og-image>https://raw.githubusercontent.com/PatilShreyas/bytemask/main/cover.png</og-image>
<og-twitter>https://raw.githubusercontent.com/PatilShreyas/bytemask/main/cover.png</og-twitter>
</variables>
<sitemap priority="0.35" change-frequency="monthly"/>
</build-profile>
<footer>
<link href="https://github.com/PatilShreyas/bytemask">View On GitHub</link>
<link href="https://github.com/PatilShreyas/bytemask/discussions">Discuss</link>
<link href="https://github.com/PatilShreyas/bytemask/issues">Create issue</link>
<link href="https://github.com/PatilShreyas/bytemask/blob/main/CONTRIBUTING.md">Contribute</link>
</footer>
</buildprofiles>
================================================
FILE: docs/Writerside/in.tree
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE instance-profile
SYSTEM "https://resources.jetbrains.com/writerside/1.0/product-profile.dtd">
<instance-profile id="in"
name="Bytemask - Android Gradle Plugin"
start-page="Introduction.md">
<toc-element topic="Introduction.md"/>
<toc-element topic="Getting-Started.md"/>
<toc-element topic="Configure.md"/>
<toc-element topic="Declaring-properties.md"/>
<toc-element topic="Read-configuration.md"/>
<toc-element topic="Android-Customization.md"/>
<toc-element toc-title="Releases"
target-for-accept-web-file-names="https://github.com/PatilShreyas/bytemask/releases"/>
</instance-profile>
================================================
FILE: docs/Writerside/redirection-rules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rules SYSTEM "https://resources.jetbrains.com/writerside/1.0/redirection-rules.dtd">
<rules>
<!-- format is as follows
<rule id="<unique id>">
<accepts>page.html</accepts>
</rule>
-->
<rule id="113781da">
<description>Created after removal of "Overview" from Bytemask - Android Gradle Plugin</description>
<accepts>Overview.html</accepts>
</rule>
<rule id="6b2dcc15">
<description>Created after removal of "Tutorial" from Bytemask - Android Gradle Plugin</description>
<accepts>Tutorial.html</accepts>
</rule>
<rule id="6b305a9">
<description>Created after removal of "Android app" from Bytemask - Android Gradle Plugin</description>
<accepts>Android-app.html</accepts>
</rule>
</rules>
================================================
FILE: docs/Writerside/topics/Android-Customization.md
================================================
# App Runtime Customization
## Initialization
Bytemask automatically gets initialized via App startup library at the time of launch.
So there is no need to initialize it explicitly. But if you want to control the initialization, follow these steps:
### 1. Disable the initializer
In the app's manifest, remove the initializer like this:
```XML
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="dev.shreyaspatil.bytemask.android.initializer.BytemaskInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
```
### 2. Initialize manually
#### 2.1 Initializing with default config (App signing config)
```Kotlin
class MyApp: Application() {
override fun onCreate() {
//...
AndroidBytemask.init(context = this)
}
}
```
#### 2.2 Initializing with custom encryption specification
If you've provided [custom encryption specification](Configure.md "Providing custom encryption key") for the encryption, you can configure the same key
for decryption.
```Kotlin
class CustomEncryptionKeyProvider : EncryptionKeyProvider {
override fun get(): Sha256DigestableKey() {
return Sha256DigestableKey("encryption_key_here")
}
}
class MyApp : Application() {
override fun onCreate() {
//...
Bytemask.init(CustomEncryptionKeyProvider())
}
}
```
================================================
FILE: docs/Writerside/topics/Configure.md
================================================
# Configure Plugin
In the module where plugin is applied, you can configure the plugin with following options:
## Configuration option
| Parameter | Description | Default value |
|-----------------------------|--------------------------------------------------|---------------------|
| `defaultPropertiesFileName` | The `.properties` file to read the strings from. | bytemask.properties |
| `className` | Class name for the generated class | BytemaskConfig |
### Variant configuration option
| Parameter | Description | Default value |
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------|
| `enableEncryption` | Whether to enable encryption for the current variant or not. | `false` |
| `encryptionKeySource` | The source of encryption key. Possible sources: <br>**1. Signing Info:** Reads the signing info (SHA-256) directly from the signing config specified in the project <br>**2. Key**: Encrypts with the specified key. | - |
| `encryptionSpec` | The specification for encryption. | Algorithm = "AES" <br> Transformation = "AES/CBC/PKCS5Padding" |
## Usage Examples
### Configure custom file name and class name
```Kotlin
bytemaskConfig {
// Strings to read from
defaultPropertiesFileName.set("bytemask.properties")
// Class name for the generated class
className.set("BytemaskConfig")
}
```
### Configure for the "release" variant with signing config
```Kotlin
android {
signingConfigs {
create("release") {
// Configure signing
}
}
}
bytemaskConfig {
// ...
configure("release") {
enableEncryption.set(true)
// This will pick signing info directly from the `android.signingConfigs`.
encryptionKeySource.set(KeySource.SigningConfig(name = "release"))
}
}
```
### Provide a custom encryption key directly without depending on signing config info
```Kotlin
bytemaskConfig {
// ...
configure("release") {
encryptionKeySource.set(KeySource.Key("encryption_key_here"))
}
}
```
### Providing encryption specification
```Kotlin
bytemaskConfig {
// ...
configure("release") {
encryptionSpec.set(EncryptionSpec(algorithm = "AES", transformation = "AES/GCM/NoPadding"))
}
}
```
### Configure for multiple flavours
```Kotlin
android {
flavorDimensions += "version"
productFlavors {
create("free") { ... }
create("paid") { ... }
}
}
bytemaskConfig {
configure("freeDebug") {}
configure("freeRelease") {}
configure("paidDebug") {}
configure("paidRelease") {}
}
```
================================================
FILE: docs/Writerside/topics/Declaring-properties.md
================================================
# Declaring properties
By default, plugin picks the properties from the `bytemask.properties` file.
If you've configured the different name
from the [configuration](Configure.md "Configure custom file name and class name"), then you'll have to create a file
with that name.
Once configuration is done, Build the project 🔨 and class will be generated.
## Example
### Global configuration
Create `.properties` file in `/app` directory
```Generic
API_KEY=AI984013oindh48
SERVER_SECRET=SUPERSECRET!!!
```
### Variant specific configuration
Create `.properties` file in variant source directory:
- `/app/src/debug`
```Generic
API_KEY=DEBUG-AI984013oindh48
SERVER_SECRET=SUPERSECRET!!!DEBUG
```
- `/app/src/release`
```Generic
API_KEY=PROD-AI984013oindh48
SERVER_SECRET=SUPERSECRET!!!PROD
```
### Flavour specific configuration
Create `.properties` file in flavour source directory:
Assuming we have `free` and `paid` variants, example:
- `/app/src/freeDebug`
```Generic
API_KEY=DEBUG-AI984013oindh48
SERVER_SECRET=SUPERSECRET!!!DEBUG
ADS_API_KEY=exaih8YJBhbhjBHJBhj
```
- `/app/src/paidRelease`
```Generic
API_KEY=PROD-AI984013oindh48
SERVER_SECRET=SUPERSECRET!!!PROD
PAYMENT_API_KEY=uguUGYGyg8G&IUG
```
> Properties will always be picked in the following order:
> 1. Flavour
> 2. Variant
> 3. Global
================================================
FILE: docs/Writerside/topics/Getting-Started.md
================================================
# Getting Started
The Gradle plugin can be only applied to the following Android modules:
- Application (`com.android.application`)
- Dynamic Feature Module (`com.android.dynamic-feature`)
> Library modules (`com.android.library`) can be used in more than one app, so libraries are not applicable for this
> use case.
{style="note"}
## Apply plugin
Apply the plugin in the app module.
### Using Plugin DSL
```Kotlin
plugins {
id("dev.shreyaspatil.bytemask.plugin") version "1.0.0-beta01"
}
```
### OR using Legacy plugin application
```Kotlin
buildscript {
repositories {
maven {
url = uri("https://plugins.gradle.org/m2/")
}
}
dependencies {
classpath("dev.shreyaspatil.bytemask:gradle-plugin:1.0.0-beta01")
}
}
apply(plugin = "dev.shreyaspatil.bytemask.plugin")
```
================================================
FILE: docs/Writerside/topics/Introduction.md
================================================
# Introduction
Bytemask is an Android Gradle Plugin that ***masks*** your secret strings for the app in the source code making it
difficult to extract from reverse engineering.
## How does it work?
### 1. Plugin - Encrypt strings at the compile time
The plugin is customizable. The default implementation of plugin encrypts strings with the public app signing info
(SHA-256) and generated the config class with the encrypted value in the byte array format in the app's source.
### 2. App - Decrypt strings in the runtime
At runtime, the app retrieves values from the configuration class. These values are decrypted using the SHA-256 hash
of the app's signing certificate. This hash is fetched in the runtime using the `PackageManager` API in Android.
> This security measure helps in the scenario of app tampering. If someone tries to reverse engineer the app and rebuild
> it with their own code (_and with a different signing key_), the app will crash at runtime. This is because the
> configuration keys are **encrypted using the app's original signing key**, and an **invalid key in the modified APK**
> will be a cause to fail the decryption.
See the flow for better understanding:

### How does it generate code?
Once you declare the secret properties in `.properties` file, it generates a class with the properties provided earlier
by encrypting them and storing it in the form of bytes.
See image here (**Left:** Property declarations, **Right:** Generated class):

### How does it look after reverse engineering on obfuscated APK?
If you build a release APK with R8 obfuscation and optimizations enabled, this is how code looks like which makes it
difficult to understand.
See the sample decompiled code (by [jadx](https://github.com/skylot/jadx))

### What happens if APK is modified by unauthorized party?
If an unauthorized developer modifies an app (APK) by decompiling and rebuilding it, they won't be able to use the
original signing key. This means the modified app will have a different signature.
Since Bytemask encrypted secrets using the app's unique SHA-256 key, any modified app trying to access these secrets
will fail (crash) because it won't have the correct key (original SHA-256) in the runtime.
It fails with `javax.crypto.BadPaddingException`

================================================
FILE: docs/Writerside/topics/Read-configuration.md
================================================
# Read configuration
By default, plugin creates a class with name `BytemaskConfig`.
If you've configured the different name
from the [configuration](Configure.md "Configure custom file name and class name"), then you'll have to use that class.
## Example
If properties file is like:
```Generic
API_KEY=Hello1234
```
In code, you can access it like
<tabs>
<code-block lang="kotlin">
fun example() {
val apiKey = BytemaskConfig.API_KEY
}
</code-block>
<code-block lang="java">
void example() {
String apiKey = BytemaskConfig.apiKey()
}
</code-block>
</tabs>

================================================
FILE: docs/Writerside/v.list
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE vars SYSTEM "https://resources.jetbrains.com/writerside/1.0/vars.dtd">
<vars>
<var name="product" value="Writerside"/>
</vars>
================================================
FILE: docs/Writerside/writerside.cfg
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ihp SYSTEM "https://resources.jetbrains.com/writerside/1.0/ihp.dtd">
<ihp version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/writerside-cfg.xsd">
<topics dir="topics"/>
<images dir="images" web-path="images"/>
<categories src="c.list"/>
<vars src="v.list"/>
<instance src="in.tree"/>
</ihp>
================================================
FILE: docs/webHelpIN2-all/HelpTOC.json
================================================
{"entities":{"pages":{"how-to":{"id":"how-to","title":"How to","url":"how-to.html","level":0,"tabIndex":0}}},"topLevelIds":["how-to"]}
================================================
FILE: docs/webHelpIN2-all/Map.jhm
================================================
<?xml version='1.0' encoding='UTF-8'?><map version="2.0"><mapID target="how-to" url="how-to.html" default="yes"/><mapID target="How to" url="how-to.html" default="no"/><mapID target="how-to.md" url="how-to.html" default="yes"/><mapID target="How+to" url="how-to.html" default="no"/></map>
================================================
FILE: docs/webHelpIN2-all/api-object-digest.json
================================================
{}
================================================
FILE: docs/webHelpIN2-all/config.json
================================================
{"productVersion":"","productWebUrl":".","productId":"in","stage":"release","downloadTitle":"Get Instance Name","keymaps":{},"searchMaxHits":75,"productName":"Instance Name"}
================================================
FILE: docs/webHelpIN2-all/current.help.version
================================================
================================================
FILE: docs/webHelpIN2-all/how-to.html
================================================
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html lang="en-US" data-preset="contrast" data-primary-color="#307FFF"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="UTF-8"><meta name="robots" content="noindex"><meta name="built-on" content="2024-06-01T09:39:48.7992198"><title>How to | Instance Name</title><script type="application/json" id="virtual-toc-data">[{"id":"before-you-start","level":0,"title":"Before you start","anchor":"#before-you-start"},{"id":"how-to-perform-a-task","level":0,"title":"How to perform a task","anchor":"#how-to-perform-a-task"}]</script><script type="application/json" id="topic-shortcuts"></script><link href="https://resources.jetbrains.com/writerside/apidoc/6.10.0-b259/app.css" rel="stylesheet"><meta name="msapplication-TileColor" content="#000000"><link rel="apple-touch-icon" sizes="180x180" href="https://jetbrains.com/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="https://jetbrains.com/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="https://jetbrains.com/favicon-16x16.png"><meta name="msapplication-TileImage" content="https://resources.jetbrains.com/storage/ui/favicons/mstile-144x144.png"><meta name="msapplication-square70x70logo" content="https://resources.jetbrains.com/storage/ui/favicons/mstile-70x70.png"><meta name="msapplication-square150x150logo" content="https://resources.jetbrains.com/storage/ui/favicons/mstile-150x150.png"><meta name="msapplication-wide310x150logo" content="https://resources.jetbrains.com/storage/ui/favicons/mstile-310x150.png"><meta name="msapplication-square310x310logo" content="https://resources.jetbrains.com/storage/ui/favicons/mstile-310x310.png"><meta name="image" content=""><!-- Open Graph --><meta property="og:title" content="How to | Instance Name"><meta property="og:description" content=""><meta property="og:image" content=""><meta property="og:site_name" content="Instance Name Help"><meta property="og:type" content="website"><meta property="og:locale" content="en_US"><meta property="og:url" content="writerside-documentation/how-to.html"><!-- End Open Graph --><!-- Twitter Card --><meta name="twitter:card" content="summary_large_image"><meta name="twitter:site" content=""><meta name="twitter:title" content="How to | Instance Name"><meta name="twitter:description" content=""><meta name="twitter:creator" content=""><meta name="twitter:image:src" content=""><!-- End Twitter Card --><!-- Schema.org WebPage --><script type="application/ld+json">{
"@context": "http://schema.org",
"@type": "WebPage",
"@id": "writerside-documentation/how-to.html#webpage",
"url": "writerside-documentation/how-to.html",
"name": "How to | Instance Name",
"description": "",
"image": "",
"inLanguage":"en-US"
}</script><!-- End Schema.org --><!-- Schema.org WebSite --><script type="application/ld+json">{
"@type": "WebSite",
"@id": "writerside-documentation/#website",
"url": "writerside-documentation/",
"name": "Instance Name Help"
}</script><!-- End Schema.org --></head><body data-id="how-to" data-main-title="How to" data-article-props="{"seeAlsoStyle":"links"}" data-template="article" data-breadcrumbs=""><div class="wrapper"><main class="panel _main"><header class="panel__header"><div class="container"><h3>Instance Name Help</h3><div class="panel-trigger"></div></div></header><section class="panel__content"><div class="container"><article class="article" data-shortcut-switcher="inactive"><h1 data-toc="how-to" id="how-to.md">How to</h1><p id="unao5z_53">A How-to article is an action-oriented type of document. It explains how to perform a specific task or solve a problem, and usually contains a sequence of steps. Start with a short introductory paragraph that explains what users will accomplish by following this procedure, what they need to perform it for, or define the target audience of the doc.</p><aside class="prompt" data-type="note" data-title="" id="unao5z_54"><p id="unao5z_55"><span class="control" id="unao5z_56">Highlight important information</span></p><p id="unao5z_57">You can change the element to <span class="emphasis" id="unao5z_58">tip</span> or <span class="emphasis" id="unao5z_59">warning</span> by renaming the style attribute below.</p></aside><section class="chapter"><h2 id="before-you-start" data-toc="before-you-start">Before you start</h2><p id="unao5z_60">It is good practice to list the prerequisites that are required or recommended.</p><p id="unao5z_61">Make sure that:</p><ul class="list _bullet" id="unao5z_62"><li class="list__item" id="unao5z_63"><p>First prerequisite</p></li><li class="list__item" id="unao5z_64"><p>Second prerequisite</p></li></ul></section><section class="chapter"><h2 id="how-to-perform-a-task" data-toc="how-to-perform-a-task">How to perform a task</h2><p id="unao5z_65">Some introductory information.</p><ol class="list _decimal" id="unao5z_66" type="1"><li class="list__item" id="unao5z_67"><p id="unao5z_68">Step with a code block</p><div class="code-block" data-lang="bash">
run this --that
</div></li><li class="list__item" id="unao5z_70"><p id="unao5z_71">Step with a <a href="https://www.jetbrains.com" id="unao5z_72" data-external="true" rel="noopener noreferrer">link</a></p></li><li class="list__item" id="unao5z_73"><p id="unao5z_74">Step with a list.</p><ul class="list _bullet" id="unao5z_75"><li class="list__item" id="unao5z_76"><p>List item</p></li><li class="list__item" id="unao5z_77"><p>List item</p></li><li class="list__item" id="unao5z_78"><p>List item</p></li></ul></li></ol></section><div class="last-modified">Last modified: 01 June 2024</div><div data-feedback-placeholder="true"></div><div class="navigation-links _bottom"></div></article><div id="disqus_thread"></div></div></section></main></div><script src="https://resources.jetbrains.com/writerside/apidoc/6.10.0-b259/app.js"></script></body></html>
================================================
FILE: docs/webHelpIN2-all/index.html
================================================
<!DOCTYPE html>
<html lang="en-US">
<meta charset="utf-8">
<title>You will be redirected shortly</title>
<meta http-equiv="refresh" content="0; url=how-to.html">
<h1>Redirecting…</h1>
<a href="how-to.html">Click here if you are not redirected.</a>
<script>location = "how-to.html"</script>
</html>
================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
kotlin = "1.9.24"
androidGradlePlugin = "8.3.2"
mavenPublish = "0.28.0"
gradlePluginPublish = "1.2.1"
appStartup = "1.1.1"
annotationJvm = "1.8.0"
spotless = "6.25.0"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
[libraries]
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
appStartup = { module = "androidx.startup:startup-runtime", version.ref = "appStartup" }
annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version.ref = "annotationJvm" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
[plugins]
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
gradle-plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "gradlePluginPublish" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
android-app = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sun Apr 21 14:01:36 IST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle-plugin/.gitignore
================================================
/build
================================================
FILE: gradle-plugin/build.gradle.kts
================================================
import com.android.build.gradle.internal.tasks.factory.dependsOn
val GROUP: String by project
val VERSION_NAME: String by project
val POM_NAME: String by project
val POM_DESCRIPTION: String by project
plugins {
`kotlin-dsl`
`java-gradle-plugin`
alias(libs.plugins.gradle.plugin.publish)
}
group = GROUP
version = VERSION_NAME
dependencies {
compileOnly(gradleApi())
compileOnly(kotlin("stdlib"))
compileOnly(libs.kotlin.gradle.plugin)
compileOnly(libs.android.gradle.plugin)
implementation(project(":bytemask-core"))
}
tasks.getByName<Test>("test") { useJUnitPlatform() }
gradlePlugin {
website.set("https://github.com/PatilShreyas/bytemask")
vcsUrl.set("https://github.com/PatilShreyas/bytemask.git")
plugins {
create("reportGenPlugin") {
id = "dev.shreyaspatil.bytemask.plugin"
displayName = POM_NAME
description = POM_DESCRIPTION
implementationClass = "dev.shreyaspatil.bytemask.plugin.BytemaskPlugin"
tags.set(listOf("android", "kotlin", "security"))
}
}
}
val generateVersionClassTask =
tasks.register("generateVersionClass") {
doLast {
val version =
project.findProperty("VERSION_NAME")?.toString() ?: error("VERSION_NAME is not set")
val file =
File(
project.projectDir,
"build/generated/src/main/kotlin/dev/shreyaspatil/bytemask/plugin/Version.kt"
)
file.parentFile.mkdirs()
file.writeText(
"""
package dev.shreyaspatil.bytemask.plugin
object PluginConfig {
const val VERSION = "$version"
}
"""
.trimIndent()
)
}
}
tasks.compileKotlin.dependsOn(generateVersionClassTask)
sourceSets { main { java { srcDir("build/generated/src/main/kotlin") } } }
================================================
FILE: gradle-plugin/gradle.properties
================================================
POM_ARTIFACT_ID=gradle-plugin
POM_NAME=Bytemask - Gradle Plugin
POM_DESCRIPTION=A gradle plugin to generates secured secrets for your application.
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/BytemaskPlugin.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.DynamicFeatureAndroidComponentsExtension
import com.android.build.api.variant.GeneratesApk
import com.android.build.api.variant.Variant
import dev.shreyaspatil.bytemask.core.encryption.EncryptionSpec
import dev.shreyaspatil.bytemask.core.encryption.Sha256DigestableKey
import dev.shreyaspatil.bytemask.plugin.config.BytemaskConfig
import dev.shreyaspatil.bytemask.plugin.config.KeySource
import dev.shreyaspatil.bytemask.plugin.task.BytemaskCodegenTask
import dev.shreyaspatil.bytemask.plugin.util.capitalized
import java.io.File
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Provider
/** Bytemask Gradle Plugin. Applies on Android projects. Entry point of the plugin. */
class BytemaskPlugin : Plugin<Project> {
override fun apply(target: Project) {
var pluginApplied = false
val config = BytemaskConfig.create(target)
target.pluginManager.withPlugin("com.android.application") {
pluginApplied = true
target.extensions.configure(ApplicationAndroidComponentsExtension::class.java) {
onVariants { variant -> handleVariant(variant, target, config) }
}
}
target.pluginManager.withPlugin("com.android.dynamic-feature") {
pluginApplied = true
target.extensions.configure(DynamicFeatureAndroidComponentsExtension::class.java) {
onVariants { variant -> handleVariant(variant, target, config) }
}
}
target.afterEvaluate {
if (!pluginApplied) {
project.logger.error(
"The bytemask Gradle plugin needs to be applied on a project with either:" +
"com.android.application, com.android.dynamic-feature, com.android.library"
)
}
}
// Add the module dependencies
target.dependencies.add(
"implementation",
"dev.shreyaspatil.bytemask:bytemask-android:${PluginConfig.VERSION}"
)
}
private fun <T> handleVariant(variant: T, project: Project, config: BytemaskConfig) where
T : Variant,
T : GeneratesApk {
val codegenTask =
project.tasks.register(
"bytemask${variant.name.capitalized()}",
BytemaskCodegenTask::class.java
) {
val propertyFileName = config.defaultPropertiesFileName.get()
bytemaskPropFiles.set(
getBytemaskPropertyFiles(
propertyFileName = propertyFileName,
buildType = variant.buildType.orEmpty(),
flavorNames = variant.productFlavors.map { it.second },
root = project.projectDir
)
)
applicationNamespace.set(variant.namespace)
className.set(config.className)
val task = this
val configForVariant = config.findConfigForVariant(variant.name)
if (configForVariant != null) {
task.enableEncryption.set(configForVariant.enableEncryption.get())
task.encryptionSpec.set(configForVariant.encryptionSpec.get())
if (configForVariant.enableEncryption.get()) {
val keySource = configForVariant.encryptionKeySource.orNull
val encryptionKey =
getEncryptionKey(
keySource = keySource,
project = project,
variant = variant
)
task.encryptionKey.set(encryptionKey)
}
} else {
// Set default encryption spec
task.enableEncryption.set(false)
task.encryptionSpec.set(
EncryptionSpec(algorithm = "AES", transformation = "AES/CBC/PKCS5Padding")
)
}
}
variant.sources.java?.addGeneratedSourceDirectory(
taskProvider = codegenTask,
wiredWith = BytemaskCodegenTask::outputDirectory
)
}
private fun <T> getEncryptionKey(
keySource: KeySource?,
project: Project,
variant: T
): Provider<Sha256DigestableKey?> where T : Variant =
project.provider {
when (keySource) {
is KeySource.SigningConfig ->
getAppSigningKeyForVariant(
project = project,
keySource = keySource,
variant = variant
)
is KeySource.Key -> Sha256DigestableKey(value = keySource.encryptionKey)
null ->
getAppSigningKeyForVariant(
project = project,
keySource = KeySource.SigningConfig(variant.name),
variant = variant
)
}
}
/** Returns the signing key for the variant. It will be used as an encryption key. */
private fun <T> getAppSigningKeyForVariant(
project: Project,
keySource: KeySource.SigningConfig,
variant: T
): Sha256DigestableKey where T : Variant {
val signingConfig =
project.extensions
.getByType(CommonExtension::class.java)
.signingConfigs
.findByName(keySource.name)
return VariantSigningKeyProvider(signingConfig, variant.name).get()
}
companion object {
/** Returns property files for Bytemask. */
fun getBytemaskPropertyFiles(
propertyFileName: String,
buildType: String,
flavorNames: List<String>,
root: File
): List<File> {
return getBytemaskPropFileLocations(propertyFileName, buildType, flavorNames).map {
root.resolve(it)
}
}
/**
* Returns possible locations of property file. It will be used to search for property file
* in the project.
*/
fun getBytemaskPropFileLocations(
propertyFileName: String,
buildType: String,
flavorNames: List<String>
): List<String> {
val fileLocations: MutableList<String> = ArrayList()
val flavorName =
flavorNames.stream().reduce("") { a, b ->
a + if (a.isEmpty()) b else b.capitalized()
}
fileLocations.add("")
fileLocations.add("src/$flavorName/$buildType")
fileLocations.add("src/$buildType/$flavorName")
fileLocations.add("src/$flavorName")
fileLocations.add("src/$buildType")
fileLocations.add("src/" + flavorName + buildType.capitalized())
fileLocations.add("src/$buildType")
var fileLocation = "src"
for (flavor in flavorNames) {
fileLocation += "/$flavor"
fileLocations.add(fileLocation)
fileLocations.add("$fileLocation/$buildType")
fileLocations.add(fileLocation + buildType.capitalized())
}
return fileLocations
.distinct()
.sortedBy { path -> path.count { it == '/' } }
.map { location: String ->
if (location.isEmpty()) location + propertyFileName
else "$location/$propertyFileName"
}
}
}
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/VariantSigningKeyProvider.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin
import com.android.build.api.dsl.ApkSigningConfig
import dev.shreyaspatil.bytemask.core.EncryptionKeyProvider
import dev.shreyaspatil.bytemask.core.encryption.Sha256DigestableKey
import java.security.KeyStore
import java.security.MessageDigest
import java.security.cert.Certificate
import java.security.cert.X509Certificate
/** Provides [EncryptionKeyProvider] for a variant. */
class VariantSigningKeyProvider(
private val config: ApkSigningConfig?,
private val variantName: String,
) : EncryptionKeyProvider {
override fun get(): Sha256DigestableKey {
checkNotNull(config) { errorMessage("Signing config not found for variant") }
checkNotNull(config.storeFile) { errorMessage("Keystore file not found") }
checkNotNull(config.storePassword) { errorMessage("Keystore password not found") }
checkNotNull(config.keyAlias) { errorMessage("Key alias not found") }
checkNotNull(config.keyPassword) { errorMessage("Key password not found") }
val ks = KeyStore.getInstance("JKS")
ks.load(config.storeFile!!.inputStream(), config.storePassword?.toCharArray())
val entry: KeyStore.PrivateKeyEntry =
ks.getEntry(
config.keyAlias,
KeyStore.PasswordProtection(config.keyPassword?.toCharArray())
) as KeyStore.PrivateKeyEntry
val certificate = entry.certificate as X509Certificate
return Sha256DigestableKey(getFingerprint(certificate, "SHA-256"))
}
/** Returns the [Certificate] fingerprint as returned by `keytool`. */
private fun getFingerprint(cert: Certificate, hashAlgorithm: String): String {
val digest: MessageDigest = MessageDigest.getInstance(hashAlgorithm)
return formatSha256(digest.digest(cert.encoded))
}
/** Formats the SHA-256 [ByteArray] to a [String]. */
private fun formatSha256(value: ByteArray): String {
val sb = StringBuilder()
val len = value.size
for (i in 0 until len) {
val num = value[i].toInt() and 0xff
if (num < 0x10) {
sb.append('0')
}
sb.append(num.toString(16))
}
return sb.toString().uppercase()
}
private val errorSuffix =
"Error occurred while configuring Bytemask for variant '$variantName':"
private fun errorMessage(message: String): String {
return "$errorSuffix $message"
}
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/codegen/ConfigClassGenerator.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin.codegen
import dev.shreyaspatil.bytemask.core.encryption.EncryptionSpec
import dev.shreyaspatil.bytemask.core.encryption.Sha256DigestableKey
import dev.shreyaspatil.bytemask.core.encryption.asPlainValue
import dev.shreyaspatil.bytemask.core.encryption.encrypt
import dev.shreyaspatil.bytemask.plugin.util.camelCase
import dev.shreyaspatil.bytemask.plugin.util.normalize
import java.io.File
/** Generates a Kotlin class file with properties and values. */
internal class ConfigClassGenerator(
private val applicationNamespace: String,
private val className: String,
private val propertyAndValuesProvider: PropertyAndValuesProvider,
private val outputDir: File,
private val encryptionDetail: EncryptionDetail?
) {
fun generate(): File {
val className = className.normalize()
val declarationProperties = getDeclarationProperties()
val helperMethods = getHelperMethods()
return createClassKtFile(className).also {
it.writeText(
"""
|package $applicationNamespace
|
|object $className {
|$declarationProperties
|$helperMethods
|}
"""
.trimMargin()
)
}
}
/**
* Creates a Kotlin file with the given [className].
*
* @param className Name of the class.
*/
private fun createClassKtFile(className: String) =
applicationNamespace
.replace(".", "/")
.let { packagePath -> outputDir.resolve(packagePath).also { it.mkdirs() } }
.resolve("$className.kt")
private fun getHelperMethods() = buildString {
if (encryptionDetail == null) return@buildString
val (encryptionSpec, _) = encryptionDetail
// The reason for introducing separate function `buildString` here is because as a part of
// code optimization by R8, it converts bytes to String already in the code which exposes
// the string representation of the bytes because `String()` is an inline function in Kotlin
// SDK. So making a non-inline function here helps in preventing this exposure of encrypted
// strings.
appendLine(
"""
| // Encryption Algorithm: ${encryptionSpec.algorithm}
| private val _bytemaskEncryptionAlgorithm = ${encryptionSpec.algorithm.asByteArrayDeclaration()}
| // Encryption Transformation: ${encryptionSpec.transformation}
| private val _bytemaskEncryptionTransformation = ${encryptionSpec.transformation.asByteArrayDeclaration()}
|
| private val encryptionSpec by lazy {
| dev.shreyaspatil.bytemask.core.encryption.EncryptionSpec(
| algorithm = buildString(_bytemaskEncryptionAlgorithm),
| transformation = buildString(_bytemaskEncryptionTransformation)
| )
| }
|
| private fun unmask(bytes: ByteArray): Lazy<String> = lazy {
| dev.shreyaspatil.bytemask.core.Bytemask.getInstance().unmask(encryptionSpec, buildString(bytes))
| }
|
| private fun buildString(bytes: ByteArray): String = java.lang.String(bytes, Charsets.UTF_8) as String
"""
.trimMargin()
)
}
/**
* Generates a property declaration with encryption if [encryptionDetail] is not null.
* Otherwise, it generates a lazy property.
*
* ```kt
* // 1. With Encryption
* @JvmStatic
* @get:JvmName("property")
* val property by unmask(byteArrayOf(1, 2, 3, 4, 5))
*
* // 2. Without Encryption
* @JvmStatic
* @get:JvmName("property")
* val property by lazy { "value" }
* ```
*/
private fun getDeclarationProperties() = buildString {
propertyAndValuesProvider.getAsMap().forEach { (property, value) ->
appendLine(
"""
|
| /**
| * $value
| */
| @JvmStatic
| @get:JvmName("${property.camelCase()}")
| ${propertyDeclaration(property, value)}
"""
.trimMargin()
)
}
}
/**
* Generates a property declaration with encryption if [encryptionDetail] is not null.
* Otherwise, it generates a lazy property.
*
* ```kt
* // 1. With Encryption
* val property by unmask(byteArrayOf(1, 2, 3, 4, 5))
*
* // 2. Without Encryption
* val property by lazy { "value" }
* ```
*/
private fun propertyDeclaration(property: String, value: String): String {
return if (encryptionDetail != null) {
"""
| val $property by unmask(${encryptAsBytes(value, encryptionDetail)})
"""
.trimIndent()
} else {
"""
| val $property by lazy { "${value.replace("$", "\\$")}" }
"""
.trimIndent()
}
}
/**
* Encrypts the [value] as bytes and returns it as a byte array declaration.
*
* ```kt
* byteArrayOf(1, 2, 3, 4, 5)
* ```
*/
private fun encryptAsBytes(value: String, encryptionDetail: EncryptionDetail) =
value
.asPlainValue()
.encrypt(encryptionDetail.encryptionSpec, encryptionDetail.encryptionKey)
.value
.asByteArrayDeclaration()
/**
* Converts the string to a byte array declaration.
*
* ```kt
* byteArrayOf(1, 2, 3, 4, 5)
* ```
*/
private fun String.asByteArrayDeclaration() =
"byteArrayOf(${encodeToByteArray().joinToString(", ")})"
data class EncryptionDetail(
val encryptionSpec: EncryptionSpec,
val encryptionKey: Sha256DigestableKey
)
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/codegen/PropertyAndValuesProvider.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin.codegen
import dev.shreyaspatil.bytemask.plugin.util.normalize
import java.io.File
import java.util.Properties
class PropertyAndValuesProvider(private val propFiles: List<File>) {
fun getAsMap(): Map<String, String> {
return propFiles
.map { Properties().apply { runCatching { load(it.inputStream()) } } }
.filter { it.isNotEmpty() }
.flatMap { it.entries }
.associate { it.key.toString().normalize() to it.value.toString() }
}
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/config/BytemaskConfig.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin.config
import dev.shreyaspatil.bytemask.core.encryption.EncryptionSpec
import dev.shreyaspatil.bytemask.plugin.config.impl.BytemaskConfigImpl
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.provider.Property
/** Configuration for Bytemask plugin. */
interface BytemaskConfig {
/** Default properties file name to retrieve properties and values from. */
val defaultPropertiesFileName: Property<String>
/** The class name of the generated class. */
val className: Property<String>
/** Configurations for each variant. */
val variantConfigs: Map<String, ByteMaskVariantConfig>
/** Configures Bytemask for a variant. */
fun configure(variant: String, config: Action<ByteMaskVariantConfig>)
/** Returns the configuration for the variant. */
fun findConfigForVariant(variant: String): ByteMaskVariantConfig?
companion object {
fun create(project: Project): BytemaskConfig =
project.extensions.create("bytemaskConfig", BytemaskConfigImpl::class.java).apply {
defaultPropertiesFileName.convention("bytemask.properties")
className.convention("BytemaskConfig")
}
}
}
/** Configuration of Bytemask for variant. */
interface ByteMaskVariantConfig {
/** Enable encryption for this variant. */
val enableEncryption: Property<Boolean>
/** Encryption spec for this variant. */
val encryptionSpec: Property<EncryptionSpec>
/** Source of the key for encryption. */
val encryptionKeySource: Property<KeySource>
}
/** Represents the source of the key. */
sealed class KeySource {
/**
* Represents the signing configuration.
*
* It means key will be retrieved from the declared signing configs for the module.
*
* @param name Name of the signing config.
*/
class SigningConfig(val name: String) : KeySource()
/**
* Represents the key.
*
* It means key will [encryptionKey].
*/
class Key(val encryptionKey: String) : KeySource()
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/config/impl/ByteMaskVariantConfigImpl.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin.config.impl
import dev.shreyaspatil.bytemask.core.encryption.EncryptionSpec
import dev.shreyaspatil.bytemask.plugin.config.ByteMaskVariantConfig
import dev.shreyaspatil.bytemask.plugin.config.KeySource
import javax.inject.Inject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.property
internal abstract class ByteMaskVariantConfigImpl
@Inject
constructor(objectFactory: ObjectFactory) : ByteMaskVariantConfig {
override val enableEncryption: Property<Boolean> =
objectFactory.property<Boolean>().convention(false)
override val encryptionSpec: Property<EncryptionSpec> =
objectFactory
.property<EncryptionSpec>()
.convention(EncryptionSpec(algorithm = "AES", transformation = "AES/CBC/PKCS5Padding"))
override val encryptionKeySource: Property<KeySource> = objectFactory.property<KeySource>()
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/config/impl/BytemaskConfigImpl.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin.config.impl
import dev.shreyaspatil.bytemask.plugin.config.ByteMaskVariantConfig
import dev.shreyaspatil.bytemask.plugin.config.BytemaskConfig
import javax.inject.Inject
import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
internal abstract class BytemaskConfigImpl
@Inject
constructor(private val objectFactory: ObjectFactory) : BytemaskConfig {
override val variantConfigs: MutableMap<String, ByteMaskVariantConfig> = mutableMapOf()
override fun configure(variant: String, config: Action<ByteMaskVariantConfig>) {
val variantConfig =
variantConfigs.getOrPut(variant) {
objectFactory.newInstance(ByteMaskVariantConfigImpl::class.java)
}
config.execute(variantConfig)
}
override fun findConfigForVariant(variant: String): ByteMaskVariantConfig? {
return variantConfigs[variant]
}
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/task/BytemaskCodegenTask.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin.task
import dev.shreyaspatil.bytemask.core.encryption.EncryptionSpec
import dev.shreyaspatil.bytemask.core.encryption.Sha256DigestableKey
import dev.shreyaspatil.bytemask.plugin.codegen.ConfigClassGenerator
import dev.shreyaspatil.bytemask.plugin.codegen.PropertyAndValuesProvider
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
/** Task to generate Kotlin class file with properties and values. */
@CacheableTask
abstract class BytemaskCodegenTask : DefaultTask() {
/** Output directory to generate Kotlin class file. */
@get:OutputDirectory abstract val outputDirectory: DirectoryProperty
/** Properties files to read properties and values from. */
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputFiles
abstract val bytemaskPropFiles: Property<Collection<File>>
/** Namespace of application / dynamic feature module. (e.g. com.example.app) */
@get:Input abstract val applicationNamespace: Property<String>
/** Class name of the generated class. (e.g. AppConfig). Default is `BytemaskConfig`. */
@get:Input abstract val className: Property<String>
/**
* Encryption key to encrypt properties. Required if [enableEncryption] is true. Provide the
* SHA-256 signature of the key by which app will be signed.
*/
@get:Input @get:Optional abstract val encryptionKey: Property<Sha256DigestableKey>
/**
* Enable encryption for properties. Default is `false`. If enabled, [encryptionKey] is
* required.
*/
@get:Input abstract val enableEncryption: Property<Boolean>
/** Encryption spec for encryption. Default is AES/CBC/PKCS5Padding. */
@get:Input abstract val encryptionSpec: Property<EncryptionSpec>
@Throws(GradleException::class)
@TaskAction
fun action() {
val propFiles = bytemaskPropFiles.get().filter { it.isFile }
if (propFiles.isEmpty()) {
val message =
"""
The Bytemask Plugin cannot function without `.properties` file.
Searched locations: ${
bytemaskPropFiles.get().joinToString { it.absolutePath }
}
"""
.trimIndent()
logger.error(message)
return
}
// Delete all existing content of outputdir.
val outputDir = outputDirectory.get().asFile
outputDir.deleteRecursively()
if (!outputDir.mkdirs()) {
throw GradleException("Failed to create folder: $outputDir")
}
val encryptionDetail = getEncryptionDetail()
// Generate code
val codegen =
ConfigClassGenerator(
propertyAndValuesProvider = PropertyAndValuesProvider(propFiles),
applicationNamespace = applicationNamespace.get(),
className = className.get(),
outputDir = outputDir,
encryptionDetail = encryptionDetail
)
val generatedFile = codegen.generate()
logger.info("Generated Bytemask config file: ${generatedFile.absolutePath}")
}
private fun getEncryptionDetail(): ConfigClassGenerator.EncryptionDetail? =
if (enableEncryption.get()) {
if (encryptionKey.orNull == null) {
throw GradleException("Encryption key is required for encryption.")
}
ConfigClassGenerator.EncryptionDetail(
encryptionSpec = encryptionSpec.get(),
encryptionKey = encryptionKey.get()
)
} else {
null
}
}
================================================
FILE: gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/util/StringExt.kt
================================================
/**
* Copyright 2024 Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.shreyaspatil.bytemask.plugin.util
/** Capitalizes the first character of the string. Example: "hello" -> "Hello" */
fun String.capitalized(): String {
return this.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
}
/**
* Converts the string to camelCase. Example:
* 1. "hello world" -> "helloWorld"
* 2. "hello_world" -> "helloWorld"
* 3. "helloWorld" -> "helloWorld"
* 4. "HelloWorld" -> "helloWorld"
*/
fun String.camelCase(): String {
return this.split(" ", "_")
.mapIndexed { index, word ->
if (index == 0) word.lowercase() else word.lowercase().capitalized()
}
.joinToString("")
}
/**
* Cleans the string by replacing special characters with underscore.
*
* Example:
* 1. "Hello!World" -> "Hello_World"
* 2. "Hey-There" -> "Hey_There"
*/
fun String.normalize(): String {
return replace("[^A-Za-z0-9 _]".toRegex(), "_")
}
/**
* Removes special characters from the string.
*
* Example:
* 1. "Hello!World" -> "HelloWorld"
* 2. "Hey-There" -> "HeyThere"
*/
fun String.removeSpecialChars(): String {
return replace("[^A-Za-z0-9 ]".toRegex(), "")
}
================================================
FILE: gradle.properties
================================================
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
kotlin.code.style=official
android.nonTransitiveRClass=true
# Lbrary configuration
GROUP=dev.shreyaspatil.bytemask
VERSION_NAME=1.0.0-beta01
SONATYPE_HOST=DEFAULT
RELEASE_SIGNING_ENABLED=true
POM_INCEPTION_YEAR=2023
POM_URL=https://github.com/PatilShreyas/bytemask/
POM_LICENSE_NAME=MIT
POM_LICENSE_URL=https://raw.githubusercontent.com/PatilShreyas/bytemask/main/LICENSE
POM_LICENSE_DIST=repo
POM_SCM_URL=https://github.com/PatilShreyas/bytemask/
POM_SCM_CONNECTION=scm:git:git://github.com/PatilShreyas/bytemask.git
POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/PatilShreyas/bytemask.git
POM_DEVELOPER_ID=PatilShreyas
POM_DEVELOPER_NAME=Shreyas Patil
POM_DEVELOPER_URL=https://github.com/PatilShreyas/
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle.kts
================================================
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven(url = "https://plugins.gradle.org/m2/")
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenLocal()
google()
mavenCentral()
}
}
rootProject.name = "ByteMask"
include(":app")
include(":bytemask-core")
include(":bytemask-android")
include(":gradle-plugin")
================================================
FILE: spotless/copyright.kt
================================================
/**
* Copyright $YEAR Shreyas Patil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
gitextract_2kwxtqo9/
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── publish_docs.yml
│ └── release.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── bytemask.properties
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── dev/
│ │ └── shreyaspatil/
│ │ └── bytemask/
│ │ └── example/
│ │ └── MainActivity.kt
│ └── res/
│ ├── drawable/
│ │ ├── ic_launcher_background.xml
│ │ └── ic_launcher_foreground.xml
│ ├── layout/
│ │ └── activity_main.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── xml/
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build.gradle.kts
├── bytemask-android/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── dev/
│ │ └── shreyaspatil/
│ │ └── bytemask/
│ │ └── android/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── dev/
│ │ └── shreyaspatil/
│ │ └── bytemask/
│ │ └── android/
│ │ ├── AndroidBytemask.kt
│ │ ├── impl/
│ │ │ └── AppSigningKeyAsEncryptionKeyProvider.kt
│ │ └── initializer/
│ │ └── BytemaskInitializer.kt
│ └── test/
│ └── java/
│ └── dev/
│ └── shreyaspatil/
│ └── bytemask/
│ └── android/
│ └── ExampleUnitTest.kt
├── bytemask-core/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ └── dev/
│ └── shreyaspatil/
│ └── bytemask/
│ └── core/
│ ├── Bytemask.kt
│ ├── EncryptionKeyProvider.kt
│ └── encryption/
│ ├── EncryptionSpec.kt
│ ├── EncryptionUtils.kt
│ ├── Sha256DigestableKey.kt
│ └── Value.kt
├── debug.keystore
├── docs/
│ ├── .idea/
│ │ ├── .gitignore
│ │ ├── modules.xml
│ │ └── vcs.xml
│ ├── Writerside/
│ │ ├── c.list
│ │ ├── cfg/
│ │ │ └── buildprofiles.xml
│ │ ├── in.tree
│ │ ├── redirection-rules.xml
│ │ ├── topics/
│ │ │ ├── Android-Customization.md
│ │ │ ├── Configure.md
│ │ │ ├── Declaring-properties.md
│ │ │ ├── Getting-Started.md
│ │ │ ├── Introduction.md
│ │ │ └── Read-configuration.md
│ │ ├── v.list
│ │ └── writerside.cfg
│ └── webHelpIN2-all/
│ ├── HelpTOC.json
│ ├── Map.jhm
│ ├── api-object-digest.json
│ ├── config.json
│ ├── current.help.version
│ ├── how-to.html
│ └── index.html
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle-plugin/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ └── dev/
│ └── shreyaspatil/
│ └── bytemask/
│ └── plugin/
│ ├── BytemaskPlugin.kt
│ ├── VariantSigningKeyProvider.kt
│ ├── codegen/
│ │ ├── ConfigClassGenerator.kt
│ │ └── PropertyAndValuesProvider.kt
│ ├── config/
│ │ ├── BytemaskConfig.kt
│ │ └── impl/
│ │ ├── ByteMaskVariantConfigImpl.kt
│ │ └── BytemaskConfigImpl.kt
│ ├── task/
│ │ └── BytemaskCodegenTask.kt
│ └── util/
│ └── StringExt.kt
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── spotless/
└── copyright.kt
Condensed preview — 88 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (141K chars).
[
{
"path": ".github/workflows/build.yml",
"chars": 438,
"preview": "name: Build\non: [push, pull_request]\n\njobs:\n build:\n name: Build\n runs-on: ubuntu-latest\n\n steps:\n - uses"
},
{
"path": ".github/workflows/publish_docs.yml",
"chars": 1666,
"preview": "name: Build documentation\n\non:\n push:\n branches: [\"main\"]\n workflow_dispatch:\n\npermissions:\n id-token: write\n pag"
},
{
"path": ".github/workflows/release.yml",
"chars": 1230,
"preview": "name: Release\non:\n push:\n tags:\n - 'v*'\n\njobs:\n build:\n name: Build\n runs-on: ubuntu-latest\n\n steps:\n"
},
{
"path": ".gitignore",
"chars": 233,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3355,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 847,
"preview": "## Feeling Awesome! Thanks for thinking about this.\n\nYou can contribute us by filing issues, bugs and PRs. You can also "
},
{
"path": "LICENSE",
"chars": 11343,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 1717,
"preview": "# Bytemask\n\nAndroid Gradle Plugin that masks your **secret strings** for the app in the source code making it difficult "
},
{
"path": "app/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "app/build.gradle.kts",
"chars": 2070,
"preview": "plugins {\n id(\"com.android.application\")\n id(\"org.jetbrains.kotlin.android\")\n id(\"dev.shreyaspatil.bytemask.plu"
},
{
"path": "app/bytemask.properties",
"chars": 81,
"preview": "API_KEY=Hello1234\nACCESS_TOKEN=ACCESS_TOKEN_HERE\nAPI_ENDPOINT=https://example.com"
},
{
"path": "app/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 1027,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "app/src/main/java/dev/shreyaspatil/bytemask/example/MainActivity.kt",
"chars": 1436,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/res/drawable/ic_launcher_background.xml",
"chars": 5606,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
"chars": 1702,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 1279,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 343,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 343,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 378,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"purple_200\">#FFBB86FC</color>\n <color name=\"purpl"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 70,
"preview": "<resources>\n <string name=\"app_name\">ByteMask</string>\n</resources>"
},
{
"path": "app/src/main/res/values/themes.xml",
"chars": 150,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <style name=\"Theme.ByteMask\" parent=\"android:Theme.Material.Ligh"
},
{
"path": "app/src/main/res/xml/backup_rules.xml",
"chars": 478,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Sample backup rules file; uncomment and customize as necessary.\n See htt"
},
{
"path": "app/src/main/res/xml/data_extraction_rules.xml",
"chars": 551,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Sample data extraction rules file; uncomment and customize as necessary.\n "
},
{
"path": "build.gradle.kts",
"chars": 775,
"preview": "plugins {\n alias(libs.plugins.android.app) apply false\n alias(libs.plugins.android.library) apply false\n alias("
},
{
"path": "bytemask-android/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "bytemask-android/build.gradle.kts",
"chars": 1151,
"preview": "@Suppress(\"DSL_SCOPE_VIOLATION\") // TODO: Remove once KTIJ-19369 is fixed\nplugins {\n alias(libs.plugins.android.libra"
},
{
"path": "bytemask-android/consumer-rules.pro",
"chars": 0,
"preview": ""
},
{
"path": "bytemask-android/gradle.properties",
"chars": 108,
"preview": "POM_ARTIFACT_ID=bytemask-android\nPOM_NAME=Bytemask - Android\nPOM_DESCRIPTION=Generates masked text resource."
},
{
"path": "bytemask-android/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "bytemask-android/src/androidTest/java/dev/shreyaspatil/bytemask/android/ExampleInstrumentedTest.kt",
"chars": 1291,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-android/src/main/AndroidManifest.xml",
"chars": 607,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "bytemask-android/src/main/java/dev/shreyaspatil/bytemask/android/AndroidBytemask.kt",
"chars": 1052,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-android/src/main/java/dev/shreyaspatil/bytemask/android/impl/AppSigningKeyAsEncryptionKeyProvider.kt",
"chars": 4042,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-android/src/main/java/dev/shreyaspatil/bytemask/android/initializer/BytemaskInitializer.kt",
"chars": 1146,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-android/src/test/java/dev/shreyaspatil/bytemask/android/ExampleUnitTest.kt",
"chars": 951,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-core/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "bytemask-core/build.gradle.kts",
"chars": 266,
"preview": "@Suppress(\"DSL_SCOPE_VIOLATION\") // TODO: Remove once KTIJ-19369 is fixed\nplugins {\n alias(libs.plugins.kotlin.jvm)\n "
},
{
"path": "bytemask-core/gradle.properties",
"chars": 102,
"preview": "POM_ARTIFACT_ID=bytemask-core\nPOM_NAME=Bytemask - Core\nPOM_DESCRIPTION=Generates masked text resource."
},
{
"path": "bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/Bytemask.kt",
"chars": 3347,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/EncryptionKeyProvider.kt",
"chars": 895,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/EncryptionSpec.kt",
"chars": 761,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/EncryptionUtils.kt",
"chars": 4398,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/Sha256DigestableKey.kt",
"chars": 1322,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "bytemask-core/src/main/java/dev/shreyaspatil/bytemask/core/encryption/Value.kt",
"chars": 1550,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "docs/.idea/.gitignore",
"chars": 47,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n"
},
{
"path": "docs/.idea/modules.xml",
"chars": 260,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n "
},
{
"path": "docs/.idea/vcs.xml",
"chars": 183,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"VcsDirectoryMappings\">\n <mapping dire"
},
{
"path": "docs/Writerside/c.list",
"chars": 233,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE categories\n SYSTEM \"https://resources.jetbrains.com/writerside/1"
},
{
"path": "docs/Writerside/cfg/buildprofiles.xml",
"chars": 1666,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<buildprofiles xsi:noNamespaceSchemaLocation=\"https://resources.jetbrains.com/wri"
},
{
"path": "docs/Writerside/in.tree",
"chars": 725,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE instance-profile\n SYSTEM \"https://resources.jetbrains.com/writer"
},
{
"path": "docs/Writerside/redirection-rules.xml",
"chars": 831,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE rules SYSTEM \"https://resources.jetbrains.com/writerside/1.0/redirectio"
},
{
"path": "docs/Writerside/topics/Android-Customization.md",
"chars": 1511,
"preview": "# App Runtime Customization\n\n## Initialization\n\nBytemask automatically gets initialized via App startup library at the t"
},
{
"path": "docs/Writerside/topics/Configure.md",
"chars": 3629,
"preview": "# Configure Plugin\n\nIn the module where plugin is applied, you can configure the plugin with following options:\n\n## Conf"
},
{
"path": "docs/Writerside/topics/Declaring-properties.md",
"chars": 1315,
"preview": "# Declaring properties\n\nBy default, plugin picks the properties from the `bytemask.properties` file.\nIf you've configure"
},
{
"path": "docs/Writerside/topics/Getting-Started.md",
"chars": 809,
"preview": "# Getting Started\n\nThe Gradle plugin can be only applied to the following Android modules:\n\n- Application (`com.android."
},
{
"path": "docs/Writerside/topics/Introduction.md",
"chars": 2463,
"preview": "# Introduction\n\nBytemask is an Android Gradle Plugin that ***masks*** your secret strings for the app in the source code"
},
{
"path": "docs/Writerside/topics/Read-configuration.md",
"chars": 602,
"preview": "# Read configuration\n\nBy default, plugin creates a class with name `BytemaskConfig`.\nIf you've configured the different "
},
{
"path": "docs/Writerside/v.list",
"chars": 180,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE vars SYSTEM \"https://resources.jetbrains.com/writerside/1.0/vars.dtd\">\n"
},
{
"path": "docs/Writerside/writerside.cfg",
"chars": 460,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE ihp SYSTEM \"https://resources.jetbrains.com/writerside/1.0/ihp.dtd\">\n\n<"
},
{
"path": "docs/webHelpIN2-all/HelpTOC.json",
"chars": 134,
"preview": "{\"entities\":{\"pages\":{\"how-to\":{\"id\":\"how-to\",\"title\":\"How to\",\"url\":\"how-to.html\",\"level\":0,\"tabIndex\":0}}},\"topLevelId"
},
{
"path": "docs/webHelpIN2-all/Map.jhm",
"chars": 288,
"preview": "<?xml version='1.0' encoding='UTF-8'?><map version=\"2.0\"><mapID target=\"how-to\" url=\"how-to.html\" default=\"yes\"/><mapID "
},
{
"path": "docs/webHelpIN2-all/api-object-digest.json",
"chars": 2,
"preview": "{}"
},
{
"path": "docs/webHelpIN2-all/config.json",
"chars": 174,
"preview": "{\"productVersion\":\"\",\"productWebUrl\":\".\",\"productId\":\"in\",\"stage\":\"release\",\"downloadTitle\":\"Get Instance Name\",\"keymaps"
},
{
"path": "docs/webHelpIN2-all/current.help.version",
"chars": 0,
"preview": ""
},
{
"path": "docs/webHelpIN2-all/how-to.html",
"chars": 5958,
"preview": "<!DOCTYPE html SYSTEM \"about:legacy-compat\">\n<html lang=\"en-US\" data-preset=\"contrast\" data-primary-color=\"#307FFF\"><hea"
},
{
"path": "docs/webHelpIN2-all/index.html",
"chars": 305,
"preview": "<!DOCTYPE html>\n<html lang=\"en-US\">\n<meta charset=\"utf-8\">\n<title>You will be redirected shortly</title>\n<meta http-equi"
},
{
"path": "gradle/libs.versions.toml",
"chars": 1552,
"preview": "[versions]\nkotlin = \"1.9.24\"\nandroidGradlePlugin = \"8.3.2\"\nmavenPublish = \"0.28.0\"\ngradlePluginPublish = \"1.2.1\"\nappStar"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Sun Apr 21 14:01:36 IST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://"
},
{
"path": "gradle-plugin/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "gradle-plugin/build.gradle.kts",
"chars": 1998,
"preview": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nval GROUP: String by project\nval VERSION_NAME: String "
},
{
"path": "gradle-plugin/gradle.properties",
"chars": 146,
"preview": "POM_ARTIFACT_ID=gradle-plugin\nPOM_NAME=Bytemask - Gradle Plugin\nPOM_DESCRIPTION=A gradle plugin to generates secured sec"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/BytemaskPlugin.kt",
"chars": 8471,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/VariantSigningKeyProvider.kt",
"chars": 3075,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/codegen/ConfigClassGenerator.kt",
"chars": 6685,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/codegen/PropertyAndValuesProvider.kt",
"chars": 1131,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/config/BytemaskConfig.kt",
"chars": 2691,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/config/impl/ByteMaskVariantConfigImpl.kt",
"chars": 1550,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/config/impl/BytemaskConfigImpl.kt",
"chars": 1523,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/task/BytemaskCodegenTask.kt",
"chars": 4667,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle-plugin/src/main/java/dev/shreyaspatil/bytemask/plugin/util/StringExt.kt",
"chars": 1755,
"preview": "/**\n * Copyright 2024 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "gradle.properties",
"chars": 791,
"preview": "org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\nandroid.useAndroidX=true\nkotlin.code.style=official\nandroid.nonTransi"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2674,
"preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "settings.gradle.kts",
"chars": 482,
"preview": "pluginManagement {\n repositories {\n google()\n mavenCentral()\n gradlePluginPortal()\n maven"
},
{
"path": "spotless/copyright.kt",
"chars": 594,
"preview": "/**\n * Copyright $YEAR Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may no"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the PatilShreyas/bytemask GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 88 files (126.2 KB), approximately 33.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.