Showing
12 changed files
with
187 additions
and
135 deletions
1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <project version="4"> | 2 | <project version="4"> |
3 | <component name="CompilerConfiguration"> | 3 | <component name="CompilerConfiguration"> |
4 | - <bytecodeTargetLevel target="17" /> | 4 | + <bytecodeTargetLevel target="21" /> |
5 | </component> | 5 | </component> |
6 | </project> | 6 | </project> |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -3,6 +3,7 @@ | ... | @@ -3,6 +3,7 @@ |
3 | <component name="GradleSettings"> | 3 | <component name="GradleSettings"> |
4 | <option name="linkedExternalProjectsSettings"> | 4 | <option name="linkedExternalProjectsSettings"> |
5 | <GradleProjectSettings> | 5 | <GradleProjectSettings> |
6 | + <option name="testRunner" value="CHOOSE_PER_TEST" /> | ||
6 | <option name="externalProjectPath" value="$PROJECT_DIR$" /> | 7 | <option name="externalProjectPath" value="$PROJECT_DIR$" /> |
7 | <option name="gradleJvm" value="jbr-17" /> | 8 | <option name="gradleJvm" value="jbr-17" /> |
8 | <option name="modules"> | 9 | <option name="modules"> | ... | ... |
1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <project version="4"> | 2 | <project version="4"> |
3 | <component name="ExternalStorageConfigurationManager" enabled="true" /> | 3 | <component name="ExternalStorageConfigurationManager" enabled="true" /> |
4 | - <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> | 4 | + <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> |
5 | <output url="file://$PROJECT_DIR$/build/classes" /> | 5 | <output url="file://$PROJECT_DIR$/build/classes" /> |
6 | </component> | 6 | </component> |
7 | </project> | 7 | </project> |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -2,13 +2,14 @@ apply plugin: 'com.android.application' | ... | @@ -2,13 +2,14 @@ apply plugin: 'com.android.application' |
2 | apply plugin: 'com.google.gms.google-services' | 2 | apply plugin: 'com.google.gms.google-services' |
3 | 3 | ||
4 | android { | 4 | android { |
5 | - compileSdkVersion 33 | 5 | + compileSdkVersion 35 |
6 | - buildToolsVersion "33.0.2" | 6 | + buildToolsVersion "35.0.0" |
7 | 7 | ||
8 | + namespace "warp.ly.android_sdk" | ||
8 | defaultConfig { | 9 | defaultConfig { |
9 | applicationId "warp.ly.android_sdk" | 10 | applicationId "warp.ly.android_sdk" |
10 | minSdkVersion 23 | 11 | minSdkVersion 23 |
11 | - targetSdkVersion 33 | 12 | + targetSdkVersion 35 |
12 | versionCode 100 | 13 | versionCode 100 |
13 | versionName "1.0.0" | 14 | versionName "1.0.0" |
14 | } | 15 | } | ... | ... |
... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
4 | Uuid=b13ade8ef743468b89a7aaa8efbfc468 | 4 | Uuid=b13ade8ef743468b89a7aaa8efbfc468 |
5 | 5 | ||
6 | # If we need to see logs in Logcat | 6 | # If we need to see logs in Logcat |
7 | -Debug=false | 7 | +Debug=true |
8 | 8 | ||
9 | # Production or Development environment of the engage server | 9 | # Production or Development environment of the engage server |
10 | # Production: https://engage.warp.ly | 10 | # Production: https://engage.warp.ly | ... | ... |
... | @@ -8,16 +8,19 @@ buildscript { | ... | @@ -8,16 +8,19 @@ buildscript { |
8 | maven { url 'https://plugins.gradle.org/m2/' } | 8 | maven { url 'https://plugins.gradle.org/m2/' } |
9 | } | 9 | } |
10 | dependencies { | 10 | dependencies { |
11 | - classpath 'com.android.tools.build:gradle:7.0.4' | 11 | + classpath 'com.android.tools.build:gradle:8.7.3' |
12 | - classpath 'com.google.gms:google-services:4.3.15' | 12 | + classpath 'com.google.gms:google-services:4.4.3' |
13 | - classpath 'com.huawei.agconnect:agcp:1.7.2.300' | 13 | + classpath 'com.huawei.agconnect:agcp:1.9.3.300' |
14 | - classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' | ||
15 | 14 | ||
16 | // NOTE: Do not place your application dependencies here; they belong | 15 | // NOTE: Do not place your application dependencies here; they belong |
17 | // in the individual module build.gradle files | 16 | // in the individual module build.gradle files |
18 | } | 17 | } |
19 | } | 18 | } |
20 | 19 | ||
20 | +plugins { | ||
21 | + id 'maven-publish' | ||
22 | +} | ||
23 | + | ||
21 | allprojects { | 24 | allprojects { |
22 | repositories { | 25 | repositories { |
23 | mavenCentral() | 26 | mavenCentral() |
... | @@ -27,5 +30,4 @@ allprojects { | ... | @@ -27,5 +30,4 @@ allprojects { |
27 | } | 30 | } |
28 | } | 31 | } |
29 | 32 | ||
30 | -apply plugin: 'io.github.gradle-nexus.publish-plugin' | ||
31 | apply from: "${rootDir}/scripts/publish-root.gradle" | 33 | apply from: "${rootDir}/scripts/publish-root.gradle" |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | distributionBase=GRADLE_USER_HOME | 1 | distributionBase=GRADLE_USER_HOME |
2 | distributionPath=wrapper/dists | 2 | distributionPath=wrapper/dists |
3 | -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip | 3 | +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip |
4 | zipStoreBase=GRADLE_USER_HOME | 4 | zipStoreBase=GRADLE_USER_HOME |
5 | zipStorePath=wrapper/dists | 5 | zipStorePath=wrapper/dists | ... | ... |
This diff is collapsed. Click to expand it.
1 | // Create variables with empty default values | 1 | // Create variables with empty default values |
2 | 2 | ||
3 | +// Central Portal credentials | ||
4 | +ext["centralPortalUsername"] = '' | ||
5 | +ext["centralPortalPassword"] = '' | ||
6 | + | ||
3 | // keyId is the last 8 characters of the GPG key | 7 | // keyId is the last 8 characters of the GPG key |
4 | ext["signing.keyId"] = '' | 8 | ext["signing.keyId"] = '' |
5 | // password is the passphrase of the GPG key | 9 | // password is the passphrase of the GPG key |
6 | ext["signing.password"] = '' | 10 | ext["signing.password"] = '' |
7 | // key is the base64 private GPG key | 11 | // key is the base64 private GPG key |
8 | ext["signing.key"] = '' | 12 | ext["signing.key"] = '' |
9 | -// osshrUsername and ossrhPassword are the account details for MavenCentral | ||
10 | -// which we’ve chosen at the Jira registration step (Sonatype site)) | ||
11 | -ext["ossrhUsername"] = '' | ||
12 | -ext["ossrhPassword"] = '' | ||
13 | -ext["sonatypeStagingProfileId"] = '' | ||
14 | 13 | ||
15 | File secretPropsFile = project.rootProject.file('local.properties') | 14 | File secretPropsFile = project.rootProject.file('local.properties') |
16 | if (secretPropsFile.exists()) { | 15 | if (secretPropsFile.exists()) { |
... | @@ -19,24 +18,11 @@ if (secretPropsFile.exists()) { | ... | @@ -19,24 +18,11 @@ if (secretPropsFile.exists()) { |
19 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } | 18 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } |
20 | p.each { name, value -> ext[name] = value } | 19 | p.each { name, value -> ext[name] = value } |
21 | } else { | 20 | } else { |
22 | - // Use system environment variables | 21 | + // Use system environment variables for signing |
23 | - ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') | ||
24 | - ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') | ||
25 | - ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') | ||
26 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') | 22 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') |
27 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD') | 23 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD') |
28 | ext["signing.key"] = System.getenv('SIGNING_KEY') | 24 | ext["signing.key"] = System.getenv('SIGNING_KEY') |
25 | + // Central Portal credentials can also come from environment | ||
26 | + ext["centralPortalUsername"] = System.getenv('CENTRAL_PORTAL_USERNAME') | ||
27 | + ext["centralPortalPassword"] = System.getenv('CENTRAL_PORTAL_PASSWORD') | ||
29 | } | 28 | } |
30 | - | ||
31 | -// Set up Sonatype repository | ||
32 | -nexusPublishing { | ||
33 | - repositories { | ||
34 | - sonatype { | ||
35 | - stagingProfileId = sonatypeStagingProfileId | ||
36 | - username = ossrhUsername | ||
37 | - password = ossrhPassword | ||
38 | - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) | ||
39 | - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) | ||
40 | - } | ||
41 | - } | ||
42 | -} | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -7,22 +7,18 @@ import android.content.Context; | ... | @@ -7,22 +7,18 @@ import android.content.Context; |
7 | import android.database.Cursor; | 7 | import android.database.Cursor; |
8 | import android.database.SQLException; | 8 | import android.database.SQLException; |
9 | import android.text.TextUtils; | 9 | import android.text.TextUtils; |
10 | -import android.util.Log; | ||
11 | 10 | ||
12 | import androidx.annotation.Nullable; | 11 | import androidx.annotation.Nullable; |
13 | 12 | ||
14 | -import net.sqlcipher.DatabaseUtils; | 13 | +import android.database.DatabaseUtils; |
15 | -import net.sqlcipher.database.SQLiteDatabase; | 14 | +import android.database.sqlite.SQLiteDatabase; |
16 | -import net.sqlcipher.database.SQLiteOpenHelper; | 15 | +import android.database.sqlite.SQLiteOpenHelper; |
17 | -import net.sqlcipher.database.SQLiteStatement; | ||
18 | 16 | ||
19 | -import java.io.File; | ||
20 | -import java.io.FileNotFoundException; | ||
21 | -import java.io.IOException; | ||
22 | import java.util.ArrayList; | 17 | import java.util.ArrayList; |
23 | import java.util.List; | 18 | import java.util.List; |
24 | 19 | ||
25 | import ly.warp.sdk.utils.constants.WarpConstants; | 20 | import ly.warp.sdk.utils.constants.WarpConstants; |
21 | +import ly.warp.sdk.utils.CryptoUtils; | ||
26 | 22 | ||
27 | public class WarplyDBHelper extends SQLiteOpenHelper { | 23 | public class WarplyDBHelper extends SQLiteOpenHelper { |
28 | 24 | ||
... | @@ -30,13 +26,8 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -30,13 +26,8 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
30 | // Constants | 26 | // Constants |
31 | // =========================================================== | 27 | // =========================================================== |
32 | 28 | ||
33 | - private enum State { | 29 | + private static final String DB_NAME = "warply_v2.db"; |
34 | - DOES_NOT_EXIST, UNENCRYPTED, ENCRYPTED | 30 | + private static final int DB_VERSION = 1; |
35 | - } | ||
36 | - | ||
37 | - private static final String DB_NAME = "warply.db"; | ||
38 | - private static final int DB_VERSION = 5; | ||
39 | - private static final String KEY_CIPHER = "tn#mpOl3v3Dy1pr@W"; | ||
40 | 31 | ||
41 | //------------------------------ Fields -----------------------------// | 32 | //------------------------------ Fields -----------------------------// |
42 | private static String TABLE_REQUESTS = "requests"; | 33 | private static String TABLE_REQUESTS = "requests"; |
... | @@ -110,7 +101,6 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -110,7 +101,6 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
110 | 101 | ||
111 | public static synchronized WarplyDBHelper getInstance(Context context) { | 102 | public static synchronized WarplyDBHelper getInstance(Context context) { |
112 | if (mDBHelperInstance == null) { | 103 | if (mDBHelperInstance == null) { |
113 | - SQLiteDatabase.loadLibs(context); | ||
114 | mDBHelperInstance = new WarplyDBHelper(context); | 104 | mDBHelperInstance = new WarplyDBHelper(context); |
115 | } | 105 | } |
116 | return mDBHelperInstance; | 106 | return mDBHelperInstance; |
... | @@ -118,19 +108,15 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -118,19 +108,15 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
118 | 108 | ||
119 | private WarplyDBHelper(Context context) { | 109 | private WarplyDBHelper(Context context) { |
120 | super(context, DB_NAME, null, DB_VERSION); | 110 | super(context, DB_NAME, null, DB_VERSION); |
121 | - State tempDatabaseState = getDatabaseState(context, DB_NAME); | ||
122 | - if (tempDatabaseState.equals(State.UNENCRYPTED)) { | ||
123 | - encrypt(context, context.getDatabasePath(DB_NAME), KEY_CIPHER.getBytes()); | ||
124 | - } | ||
125 | } | 111 | } |
126 | 112 | ||
127 | /** | 113 | /** |
128 | - * If database connection already initialized, return the db. Else create a | 114 | + * Get standard SQLite database connection |
129 | - * new one | ||
130 | */ | 115 | */ |
131 | private SQLiteDatabase getDb() { | 116 | private SQLiteDatabase getDb() { |
132 | - if (mDb == null) | 117 | + if (mDb == null || !mDb.isOpen()) { |
133 | - mDb = getWritableDatabase(KEY_CIPHER); | 118 | + mDb = getWritableDatabase(); |
119 | + } | ||
134 | return mDb; | 120 | return mDb; |
135 | } | 121 | } |
136 | 122 | ||
... | @@ -215,13 +201,13 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -215,13 +201,13 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
215 | return false; | 201 | return false; |
216 | } | 202 | } |
217 | 203 | ||
218 | - //------------------------------ Auth -----------------------------// | 204 | + //------------------------------ Auth (with field-level encryption) -----------------------------// |
219 | public synchronized void saveClientAccess(String clientId, String clientSecret) { | 205 | public synchronized void saveClientAccess(String clientId, String clientSecret) { |
220 | ContentValues values = new ContentValues(); | 206 | ContentValues values = new ContentValues(); |
221 | if (!TextUtils.isEmpty(clientId)) | 207 | if (!TextUtils.isEmpty(clientId)) |
222 | - values.put(KEY_CLIENT_ID, clientId); | 208 | + values.put(KEY_CLIENT_ID, CryptoUtils.encrypt(clientId)); |
223 | if (!TextUtils.isEmpty(clientSecret)) | 209 | if (!TextUtils.isEmpty(clientSecret)) |
224 | - values.put(KEY_CLIENT_SECRET, clientSecret); | 210 | + values.put(KEY_CLIENT_SECRET, CryptoUtils.encrypt(clientSecret)); |
225 | if (isTableNotEmpty(TABLE_CLIENT)) | 211 | if (isTableNotEmpty(TABLE_CLIENT)) |
226 | update(TABLE_CLIENT, values); | 212 | update(TABLE_CLIENT, values); |
227 | else | 213 | else |
... | @@ -231,9 +217,9 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -231,9 +217,9 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
231 | public synchronized void saveAuthAccess(String accessToken, String refreshToken) { | 217 | public synchronized void saveAuthAccess(String accessToken, String refreshToken) { |
232 | ContentValues values = new ContentValues(); | 218 | ContentValues values = new ContentValues(); |
233 | if (!TextUtils.isEmpty(accessToken)) | 219 | if (!TextUtils.isEmpty(accessToken)) |
234 | - values.put(KEY_ACCESS_TOKEN, accessToken); | 220 | + values.put(KEY_ACCESS_TOKEN, CryptoUtils.encrypt(accessToken)); |
235 | if (!TextUtils.isEmpty(refreshToken)) | 221 | if (!TextUtils.isEmpty(refreshToken)) |
236 | - values.put(KEY_REFRESH_TOKEN, refreshToken); | 222 | + values.put(KEY_REFRESH_TOKEN, CryptoUtils.encrypt(refreshToken)); |
237 | if (isTableNotEmpty(TABLE_AUTH)) | 223 | if (isTableNotEmpty(TABLE_AUTH)) |
238 | update(TABLE_AUTH, values); | 224 | update(TABLE_AUTH, values); |
239 | else | 225 | else |
... | @@ -244,9 +230,9 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -244,9 +230,9 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
244 | public synchronized String getAuthValue(String columnName) { | 230 | public synchronized String getAuthValue(String columnName) { |
245 | String columnValue = ""; | 231 | String columnValue = ""; |
246 | Cursor cursor = getDb().query(TABLE_AUTH, new String[]{columnName}, null, null, null, null, null); | 232 | Cursor cursor = getDb().query(TABLE_AUTH, new String[]{columnName}, null, null, null, null, null); |
247 | - if (cursor != null) { | 233 | + if (cursor != null && cursor.moveToFirst()) { |
248 | - cursor.moveToFirst(); | 234 | + String encryptedValue = cursor.getString(cursor.getColumnIndex(columnName)); |
249 | - columnValue = cursor.getString(cursor.getColumnIndex(columnName)); | 235 | + columnValue = CryptoUtils.decrypt(encryptedValue); |
250 | cursor.close(); | 236 | cursor.close(); |
251 | } | 237 | } |
252 | return columnValue; | 238 | return columnValue; |
... | @@ -256,9 +242,9 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -256,9 +242,9 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
256 | public synchronized String getClientValue(String columnName) { | 242 | public synchronized String getClientValue(String columnName) { |
257 | String columnValue = ""; | 243 | String columnValue = ""; |
258 | Cursor cursor = getDb().query(TABLE_CLIENT, new String[]{columnName}, null, null, null, null, null); | 244 | Cursor cursor = getDb().query(TABLE_CLIENT, new String[]{columnName}, null, null, null, null, null); |
259 | - if (cursor != null) { | 245 | + if (cursor != null && cursor.moveToFirst()) { |
260 | - cursor.moveToFirst(); | 246 | + String encryptedValue = cursor.getString(cursor.getColumnIndex(columnName)); |
261 | - columnValue = cursor.getString(cursor.getColumnIndex(columnName)); | 247 | + columnValue = CryptoUtils.decrypt(encryptedValue); |
262 | cursor.close(); | 248 | cursor.close(); |
263 | } | 249 | } |
264 | return columnValue; | 250 | return columnValue; |
... | @@ -343,15 +329,15 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -343,15 +329,15 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
343 | } | 329 | } |
344 | 330 | ||
345 | public synchronized long getRequestsInQueueCount() { | 331 | public synchronized long getRequestsInQueueCount() { |
346 | - return DatabaseUtils.queryNumEntries(getReadableDatabase(KEY_CIPHER), TABLE_REQUESTS); | 332 | + return DatabaseUtils.queryNumEntries(getReadableDatabase(), TABLE_REQUESTS); |
347 | } | 333 | } |
348 | 334 | ||
349 | public synchronized long getPushRequestsInQueueCount() { | 335 | public synchronized long getPushRequestsInQueueCount() { |
350 | - return DatabaseUtils.queryNumEntries(getReadableDatabase(KEY_CIPHER), TABLE_PUSH_REQUESTS); | 336 | + return DatabaseUtils.queryNumEntries(getReadableDatabase(), TABLE_PUSH_REQUESTS); |
351 | } | 337 | } |
352 | 338 | ||
353 | public synchronized long getPushAckRequestsInQueueCount() { | 339 | public synchronized long getPushAckRequestsInQueueCount() { |
354 | - return DatabaseUtils.queryNumEntries(getReadableDatabase(KEY_CIPHER), TABLE_PUSH_ACK_REQUESTS); | 340 | + return DatabaseUtils.queryNumEntries(getReadableDatabase(), TABLE_PUSH_ACK_REQUESTS); |
355 | } | 341 | } |
356 | 342 | ||
357 | public synchronized void deleteRequests(Long... ids) { | 343 | public synchronized void deleteRequests(Long... ids) { |
... | @@ -483,73 +469,11 @@ public class WarplyDBHelper extends SQLiteOpenHelper { | ... | @@ -483,73 +469,11 @@ public class WarplyDBHelper extends SQLiteOpenHelper { |
483 | 469 | ||
484 | tags = new ArrayList<>(cursor.getCount()); | 470 | tags = new ArrayList<>(cursor.getCount()); |
485 | while (cursor.moveToNext()) { | 471 | while (cursor.moveToNext()) { |
486 | - tags.add(cursor.getString(cursor | 472 | + tags.add(cursor.getString(cursor.getColumnIndex(KEY_TAG))); |
487 | - .getColumnIndex(KEY_TAG))); | ||
488 | } | 473 | } |
489 | cursor.close(); | 474 | cursor.close(); |
490 | } | 475 | } |
491 | return tags != null ? tags.toArray(new String[tags.size()]) : null; | 476 | return tags != null ? tags.toArray(new String[tags.size()]) : null; |
492 | } | 477 | } |
493 | 478 | ||
494 | - private State getDatabaseState(Context context, String dbName) { | ||
495 | - SQLiteDatabase.loadLibs(context); | ||
496 | - | ||
497 | - return (getDatabaseState(context.getDatabasePath(dbName))); | ||
498 | - } | ||
499 | - | ||
500 | - private static State getDatabaseState(File dbPath) { | ||
501 | - if (dbPath.exists()) { | ||
502 | - SQLiteDatabase db = null; | ||
503 | - try { | ||
504 | - db = SQLiteDatabase.openDatabase(dbPath.getAbsolutePath(), "", null, SQLiteDatabase.OPEN_READONLY); | ||
505 | - db.getVersion(); | ||
506 | - | ||
507 | - return (State.UNENCRYPTED); | ||
508 | - } catch (Exception e) { | ||
509 | - return (State.ENCRYPTED); | ||
510 | - } finally { | ||
511 | - if (db != null) { | ||
512 | - db.close(); | ||
513 | - } | ||
514 | - } | ||
515 | - } | ||
516 | - | ||
517 | - return (State.DOES_NOT_EXIST); | ||
518 | - } | ||
519 | - | ||
520 | - private void encrypt(Context context, File originalFile, byte[] passphrase) { | ||
521 | - SQLiteDatabase.loadLibs(context); | ||
522 | - | ||
523 | - try { | ||
524 | - if (originalFile.exists()) { | ||
525 | - File newFile = File.createTempFile("sqlcipherutils", "tmp", context.getCacheDir()); | ||
526 | - SQLiteDatabase db = SQLiteDatabase.openDatabase(originalFile.getAbsolutePath(), | ||
527 | - "", null, SQLiteDatabase.OPEN_READWRITE); | ||
528 | - int version = db.getVersion(); | ||
529 | - | ||
530 | - db.close(); | ||
531 | - | ||
532 | - db = SQLiteDatabase.openDatabase(newFile.getAbsolutePath(), passphrase, | ||
533 | - null, SQLiteDatabase.OPEN_READWRITE, null, null); | ||
534 | - | ||
535 | - final SQLiteStatement st = db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''"); | ||
536 | - | ||
537 | - st.bindString(1, originalFile.getAbsolutePath()); | ||
538 | - st.execute(); | ||
539 | - | ||
540 | - db.rawExecSQL("SELECT sqlcipher_export('main', 'plaintext')"); | ||
541 | - db.rawExecSQL("DETACH DATABASE plaintext"); | ||
542 | - db.setVersion(version); | ||
543 | - st.close(); | ||
544 | - db.close(); | ||
545 | - | ||
546 | - originalFile.delete(); | ||
547 | - newFile.renameTo(originalFile); | ||
548 | - } else { | ||
549 | - throw new FileNotFoundException(originalFile.getAbsolutePath() + " not found"); | ||
550 | - } | ||
551 | - } catch (IOException ex) { | ||
552 | - Log.v("WarplyDB Exception: ", ex.getMessage()); | ||
553 | - } | ||
554 | - } | ||
555 | } | 479 | } | ... | ... |
1 | +package ly.warp.sdk.utils; | ||
2 | + | ||
3 | +import android.security.keystore.KeyGenParameterSpec; | ||
4 | +import android.security.keystore.KeyProperties; | ||
5 | +import android.util.Base64; | ||
6 | +import android.util.Log; | ||
7 | + | ||
8 | +import java.security.KeyStore; | ||
9 | + | ||
10 | +import javax.crypto.Cipher; | ||
11 | +import javax.crypto.KeyGenerator; | ||
12 | +import javax.crypto.SecretKey; | ||
13 | +import javax.crypto.spec.GCMParameterSpec; | ||
14 | + | ||
15 | +/** | ||
16 | + * Utility class for encrypting and decrypting sensitive data using Android Keystore | ||
17 | + */ | ||
18 | +public class CryptoUtils { | ||
19 | + | ||
20 | + private static final String TAG = "CryptoUtils"; | ||
21 | + private static final String TRANSFORMATION = "AES/GCM/NoPadding"; | ||
22 | + private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; | ||
23 | + private static final String KEY_ALIAS = "tn#mpOl3v3Dy1pr@W"; | ||
24 | + private static final int GCM_IV_LENGTH = 12; | ||
25 | + private static final int GCM_TAG_LENGTH = 16; | ||
26 | + | ||
27 | + /** | ||
28 | + * Encrypt a plain text string | ||
29 | + * | ||
30 | + * @param plainText The text to encrypt | ||
31 | + * @return Base64 encoded encrypted string, or original text if encryption fails | ||
32 | + */ | ||
33 | + public static String encrypt(String plainText) { | ||
34 | + if (plainText == null || plainText.isEmpty()) { | ||
35 | + return plainText; | ||
36 | + } | ||
37 | + | ||
38 | + try { | ||
39 | + SecretKey secretKey = getOrCreateSecretKey(); | ||
40 | + Cipher cipher = Cipher.getInstance(TRANSFORMATION); | ||
41 | + cipher.init(Cipher.ENCRYPT_MODE, secretKey); | ||
42 | + | ||
43 | + byte[] iv = cipher.getIV(); | ||
44 | + byte[] encryptedData = cipher.doFinal(plainText.getBytes("UTF-8")); | ||
45 | + | ||
46 | + // Combine IV + encrypted data | ||
47 | + byte[] combined = new byte[iv.length + encryptedData.length]; | ||
48 | + System.arraycopy(iv, 0, combined, 0, iv.length); | ||
49 | + System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length); | ||
50 | + | ||
51 | + return Base64.encodeToString(combined, Base64.DEFAULT); | ||
52 | + } catch (Exception e) { | ||
53 | + Log.e(TAG, "Encryption failed, returning original text", e); | ||
54 | + // Return original text if encryption fails - graceful degradation | ||
55 | + return plainText; | ||
56 | + } | ||
57 | + } | ||
58 | + | ||
59 | + /** | ||
60 | + * Decrypt an encrypted string | ||
61 | + * | ||
62 | + * @param encryptedText The Base64 encoded encrypted text | ||
63 | + * @return Decrypted plain text, or original text if decryption fails | ||
64 | + */ | ||
65 | + public static String decrypt(String encryptedText) { | ||
66 | + if (encryptedText == null || encryptedText.isEmpty()) { | ||
67 | + return encryptedText; | ||
68 | + } | ||
69 | + | ||
70 | + try { | ||
71 | + SecretKey secretKey = getOrCreateSecretKey(); | ||
72 | + byte[] combined = Base64.decode(encryptedText, Base64.DEFAULT); | ||
73 | + | ||
74 | + // Extract IV and encrypted data | ||
75 | + byte[] iv = new byte[GCM_IV_LENGTH]; | ||
76 | + byte[] encryptedData = new byte[combined.length - GCM_IV_LENGTH]; | ||
77 | + System.arraycopy(combined, 0, iv, 0, GCM_IV_LENGTH); | ||
78 | + System.arraycopy(combined, GCM_IV_LENGTH, encryptedData, 0, encryptedData.length); | ||
79 | + | ||
80 | + Cipher cipher = Cipher.getInstance(TRANSFORMATION); | ||
81 | + GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); | ||
82 | + cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); | ||
83 | + | ||
84 | + byte[] decryptedData = cipher.doFinal(encryptedData); | ||
85 | + return new String(decryptedData, "UTF-8"); | ||
86 | + } catch (Exception e) { | ||
87 | + Log.e(TAG, "Decryption failed, returning original text", e); | ||
88 | + // Return original text if decryption fails - might be unencrypted legacy data | ||
89 | + return encryptedText; | ||
90 | + } | ||
91 | + } | ||
92 | + | ||
93 | + /** | ||
94 | + * Get or create the secret key in Android Keystore | ||
95 | + */ | ||
96 | + private static SecretKey getOrCreateSecretKey() throws Exception { | ||
97 | + KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); | ||
98 | + keyStore.load(null); | ||
99 | + | ||
100 | + if (!keyStore.containsAlias(KEY_ALIAS)) { | ||
101 | + KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE); | ||
102 | + KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS, | ||
103 | + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) | ||
104 | + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) | ||
105 | + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) | ||
106 | + .setRandomizedEncryptionRequired(true) | ||
107 | + .build(); | ||
108 | + keyGenerator.init(keyGenParameterSpec); | ||
109 | + keyGenerator.generateKey(); | ||
110 | + } | ||
111 | + | ||
112 | + return ((KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null)).getSecretKey(); | ||
113 | + } | ||
114 | + | ||
115 | + /** | ||
116 | + * Check if a string appears to be encrypted (Base64 format) | ||
117 | + * | ||
118 | + * @param text The text to check | ||
119 | + * @return true if text appears to be encrypted | ||
120 | + */ | ||
121 | + public static boolean isEncrypted(String text) { | ||
122 | + if (text == null || text.isEmpty()) { | ||
123 | + return false; | ||
124 | + } | ||
125 | + | ||
126 | + try { | ||
127 | + byte[] decoded = Base64.decode(text, Base64.DEFAULT); | ||
128 | + // Check if decoded length is reasonable for encrypted data (IV + data + tag) | ||
129 | + return decoded.length > GCM_IV_LENGTH + GCM_TAG_LENGTH; | ||
130 | + } catch (Exception e) { | ||
131 | + return false; | ||
132 | + } | ||
133 | + } | ||
134 | +} |
-
Please register or login to post a comment