This commit is contained in:
CounterFire2023 2023-08-17 15:31:07 +08:00
parent 2fc94d2a56
commit 2fd2850fbd
13 changed files with 555 additions and 27 deletions

File diff suppressed because one or more lines are too long

View File

@ -12,13 +12,14 @@ function promiseCb(funId, promiseFun, dataParser) {
} }
/** /**
* oauth login before init internal wallet * oauth login before init internal wallet
* @param {*} channel 0: google, 1: apple, 2: tiktok, 3: facebook, 4: twitter 5: tg * @param {*} channel 0: google, 1: apple, 2: tiktok, 3: facebook, 4: twitter 5: tg, 6: email, 7: discord 10: client
*/ */
function walletLogin(funId, channel) { function walletLogin(funId, channel, env) {
channel = parseInt(channel); channel = parseInt(channel);
env = env || 'dev';
console.log('walletLogin: ' + channel); console.log('walletLogin: ' + channel);
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet; const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
promiseCb(funId, wallet.preLogin(channel)); promiseCb(funId, wallet.preLogin(channel, env));
} }
/** /**
* init internal wallet with password * init internal wallet with password
@ -587,3 +588,15 @@ function restorePassLocal(funId, key) {
function getLocalPassState(funId, key) { function getLocalPassState(funId, key) {
promiseCb(funId, jc.wallet.nativeSvr.passStorageState(key)); promiseCb(funId, jc.wallet.nativeSvr.passStorageState(key));
} }
function saveLocalVal(funId, key, val) {
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
wallet.saveLocalItem(key, val);
return JSON.stringify({ errcode: 0, data: 1 });
}
function loadLocalVal(funId, key) {
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
let val = wallet.loadLocalItem(key);
return JSON.stringify({ errcode: 0, data: val });
}

View File

@ -46,7 +46,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:screenOrientation="sensorLandscape" android:screenOrientation="sensorLandscape"
android:theme="@style/CEBGThemeSelector" android:theme="@style/CebgMainActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density"
android:resizeableActivity="false" android:resizeableActivity="false"
@ -103,6 +103,9 @@
<activity <activity
android:name=".activity.WebPageActivity" android:name=".activity.WebPageActivity"
android:theme="@style/WebViewTheme" /> android:theme="@style/WebViewTheme" />
<activity
android:name=".activity.BiometricActivity"
android:theme="@style/AppCompatTranslucent" />
<activity <activity
android:name=".activity.CustomCaptureActivity" android:name=".activity.CustomCaptureActivity"
android:theme="@style/CaptureTheme" /> android:theme="@style/CaptureTheme" />
@ -227,7 +230,7 @@
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" /> android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<queries> <queries>
<package android:name="com.zhiliaoapp.musically" /> <package android:name="com.zhiliaoapp.musically" />
<package android:name="com.ss.android.ugc.trill" /> <package android:name="com.ss.android.ugc.trill" />

View File

@ -1,5 +1,8 @@
package com.cege.games.release; package com.cege.games.release;
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
import android.Manifest; import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -7,10 +10,10 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.view.Window; import android.view.Window;
import android.widget.Toast; import android.widget.Toast;
@ -19,6 +22,8 @@ import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
@ -28,7 +33,9 @@ import com.bytedance.sdk.open.tiktok.authorize.model.Authorization;
import com.bytedance.sdk.open.tiktok.base.MediaContent; import com.bytedance.sdk.open.tiktok.base.MediaContent;
import com.bytedance.sdk.open.tiktok.base.VideoObject; import com.bytedance.sdk.open.tiktok.base.VideoObject;
import com.bytedance.sdk.open.tiktok.share.Share; import com.bytedance.sdk.open.tiktok.share.Share;
import com.cege.games.release.activity.BiometricActivity;
import com.cege.games.release.activity.CustomCaptureActivity; import com.cege.games.release.activity.CustomCaptureActivity;
import com.cege.games.release.activity.UnityPlayerActivity;
import com.cege.games.release.activity.WebPageActivity; import com.cege.games.release.activity.WebPageActivity;
import com.cege.games.release.apple.AppleLoginActivity; import com.cege.games.release.apple.AppleLoginActivity;
import com.cege.games.release.dialog.QRCodeActivity; import com.cege.games.release.dialog.QRCodeActivity;
@ -56,12 +63,14 @@ import com.jc.jcfw.JcSDK;
import com.jc.jcfw.appauth.AuthStateManager; import com.jc.jcfw.appauth.AuthStateManager;
import com.jc.jcfw.appauth.JConfiguration; import com.jc.jcfw.appauth.JConfiguration;
import com.jc.jcfw.google.PayClient; import com.jc.jcfw.google.PayClient;
import com.jc.jcfw.util.FileUtils; import com.jc.jcfw.security.BiometricHelper;
import com.jc.jcfw.security.CryptographyManager;
import com.jc.jcfw.security.CryptographyManagerImpl;
import com.jc.jcfw.security.EncryptedData;
import com.jc.jcfw.util.JsonUtils; import com.jc.jcfw.util.JsonUtils;
import com.king.zxing.CameraScan; import com.king.zxing.CameraScan;
import com.king.zxing.util.CodeUtils; import com.king.zxing.util.CodeUtils;
import com.king.zxing.util.LogUtils; import com.king.zxing.util.LogUtils;
import com.unity3d.player.UnityPlayerActivity;
import net.openid.appauth.AppAuthConfiguration; import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthState; import net.openid.appauth.AuthState;
@ -93,6 +102,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.Cipher;
import pub.devrel.easypermissions.AfterPermissionGranted; import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions; import pub.devrel.easypermissions.EasyPermissions;
@ -136,7 +147,6 @@ public class MainActivity extends UnityPlayerActivity
private CountDownLatch mAuthIntentLatch = new CountDownLatch(1); private CountDownLatch mAuthIntentLatch = new CountDownLatch(1);
private TikTokOpenApi tiktokOpenApi; private TikTokOpenApi tiktokOpenApi;
public String getFunId() { public String getFunId() {
return funId; return funId;
} }
@ -150,6 +160,8 @@ public class MainActivity extends UnityPlayerActivity
private FirebaseAnalytics mFirebaseAnalytics; private FirebaseAnalytics mFirebaseAnalytics;
private AppEventsLogger fbLogger; private AppEventsLogger fbLogger;
private BiometricPrompt.PromptInfo promptInfo;
private CryptographyManager cryptographyManager;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -192,6 +204,8 @@ public class MainActivity extends UnityPlayerActivity
PayClient payClient = PayClient.getInstance(); PayClient payClient = PayClient.getInstance();
payClient.init(this); payClient.init(this);
promptInfo = BiometricHelper.createPromptInfo(this);
cryptographyManager = new CryptographyManagerImpl();
} }
@Override @Override
@ -270,17 +284,7 @@ public class MainActivity extends UnityPlayerActivity
} }
// begin for unity // begin for unity
@Override
protected void onNewIntent(Intent intent) {
// To support deep linking, we need to make sure that the client can get access
// to
// the last sent intent. The clients access this through a JNI api that allows
// them
// to get the intent set on launch. To update that after launch we have to
// manually
// replace the intent with the one caught here.
setIntent(intent);
}
protected void onLoadNativeLibraries() { protected void onLoadNativeLibraries() {
try { try {
@ -851,11 +855,15 @@ public class MainActivity extends UnityPlayerActivity
public void showPage(String fid, final String url) { public void showPage(String fid, final String url) {
runOnUiThread(() -> { runOnUiThread(() -> {
Log.i(TAG, "show page: " + url); Log.i(TAG, "show page: " + url);
Intent intent = new Intent(this, WebPageActivity.class); // Intent intent = new Intent(this, WebPageActivity.class);
intent.putExtra("url", url); // intent.putExtra("url", url);
startActivity(intent); // Intent intent = new Intent(this, BiometricActivity.class);
// intent.putExtra("action", "encrypt");
// intent.putExtra("key", "1111");
// startActivity(intent);
// picker video file and share to tiktok // picker video file and share to tiktok
// openFileSelector(); // openFileSelector();
authenticateToEncrypt("1111");
}); });
} }
@ -902,4 +910,32 @@ public class MainActivity extends UnityPlayerActivity
startActivityForResult(intent, FILE_SELECTOR_CODE); startActivityForResult(intent, FILE_SELECTOR_CODE);
} }
public void authenticateToEncrypt(String text) {
if (BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager
.BIOMETRIC_SUCCESS) {
Cipher cipher = cryptographyManager.getInitializedCipherForEncryption("cebg_wallet_key");
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(MainActivity.this, _cipher -> {
EncryptedData encryptedData = cryptographyManager.encryptData(text, _cipher);
String encryptedString = Base64.encodeToString(encryptedData.getCiphertext(), Base64.DEFAULT);
String ivString = Base64.encodeToString(encryptedData.getInitializationVector(), Base64.DEFAULT);
Log.i(TAG, "encrypted msg: " + encryptedString);
Log.i(TAG, "encrypted iv: " + ivString);
});
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
}
}
public void authenticateToDecrypt(String text, String iv) {
if (BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager
.BIOMETRIC_SUCCESS) {
byte[] ivData = Base64.decode(iv, Base64.DEFAULT);
byte[] textData = Base64.decode(text, Base64.DEFAULT);
Cipher cipher = cryptographyManager.getInitializedCipherForDecryption("cebg_wallet_key", ivData);
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(MainActivity.this, _cipher -> {
String decryptedMsg = cryptographyManager.decryptData(textData, _cipher);
Log.i(TAG, "decrypted msg: " + decryptedMsg);
});
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
}
}
} }

View File

@ -0,0 +1,76 @@
package com.cege.games.release.activity;
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
import android.content.Intent;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import com.cege.games.release.R;
import com.jc.jcfw.security.BiometricHelper;
import com.jc.jcfw.security.CryptographyManager;
import com.jc.jcfw.security.CryptographyManagerImpl;
import com.jc.jcfw.security.EncryptedData;
import javax.crypto.Cipher;
public class BiometricActivity extends AppCompatActivity {
private static final String TAG = BiometricActivity.class.getSimpleName();
private BiometricPrompt.PromptInfo promptInfo;
private CryptographyManager cryptographyManager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_biometric);
Log.i(TAG, "onCreate: " + getIntent().getDataString());
promptInfo = BiometricHelper.createPromptInfo(this);
cryptographyManager = new CryptographyManagerImpl();
Intent intent = getIntent();
String action = intent.getStringExtra("action");
String key = intent.getStringExtra("key");
// check if action is exists
// if ("encrypt".equals(action)) {
// authenticateToEncrypt(key);
// } else if ("decrypt".equals(action)) {
// String iv = intent.getStringExtra("iv");
// authenticateToDecrypt(key, iv);
// }
}
public void authenticateToEncrypt(String text) {
if (BiometricManager.from(this)
.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
Cipher cipher = cryptographyManager.getInitializedCipherForEncryption("cebg_wallet_key");
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(this, _cipher -> {
EncryptedData encryptedData = cryptographyManager.encryptData(text, _cipher);
String encryptedString = Base64.encodeToString(encryptedData.getCiphertext(), Base64.DEFAULT);
String ivString = Base64.encodeToString(encryptedData.getInitializationVector(), Base64.DEFAULT);
Log.i(TAG, "encrypted msg: " + encryptedString);
Log.i(TAG, "encrypted iv: " + ivString);
});
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
}
}
public void authenticateToDecrypt(String text, String iv) {
if (BiometricManager.from(this)
.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
byte[] ivData = Base64.decode(iv, Base64.DEFAULT);
byte[] textData = Base64.decode(text, Base64.DEFAULT);
Cipher cipher = cryptographyManager.getInitializedCipherForDecryption("cebg_wallet_key", ivData);
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(this, _cipher -> {
String decryptedMsg = cryptographyManager.decryptData(textData, _cipher);
Log.i(TAG, "decrypted msg: " + decryptedMsg);
});
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
}
}
}

View File

@ -0,0 +1,168 @@
package com.cege.games.release.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Window;
import androidx.appcompat.app.AppCompatActivity;
import com.unity3d.player.IUnityPlayerLifecycleEvents;
import com.unity3d.player.MultiWindowSupport;
import com.unity3d.player.UnityPlayer;
public class UnityPlayerActivity extends AppCompatActivity implements IUnityPlayerLifecycleEvents
{
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
// The command line arguments are passed as a string, separated by spaces
// UnityPlayerActivity calls this from 'onCreate'
// Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
// See https://docs.unity3d.com/Manual/CommandLineArguments.html
// @param cmdLine the current command line arguments, may be null
// @return the modified command line string or null
protected String updateUnityCommandLineArguments(String cmdLine)
{
return cmdLine;
}
// Setup activity layout
@Override protected void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
getIntent().putExtra("unity", cmdLine);
mUnityPlayer = new UnityPlayer(this, this);
setContentView(mUnityPlayer);
mUnityPlayer.requestFocus();
}
// When Unity player unloaded move task to background
@Override public void onUnityPlayerUnloaded() {
moveTaskToBack(true);
}
// Callback before Unity player process is killed
@Override public void onUnityPlayerQuitted() {
}
@Override protected void onNewIntent(Intent intent) {
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
super.onNewIntent(intent);
setIntent(intent);
mUnityPlayer.newIntent(intent);
}
// Quit Unity
@Override protected void onDestroy ()
{
mUnityPlayer.destroy();
super.onDestroy();
}
// If the activity is in multi window mode or resizing the activity is allowed we will use
// onStart/onStop (the visibility callbacks) to determine when to pause/resume.
// Otherwise it will be done in onPause/onResume as Unity has done historically to preserve
// existing behavior.
@Override protected void onStop()
{
super.onStop();
if (!MultiWindowSupport.getAllowResizableWindow(this))
return;
mUnityPlayer.pause();
}
@Override protected void onStart()
{
super.onStart();
if (!MultiWindowSupport.getAllowResizableWindow(this))
return;
mUnityPlayer.resume();
}
// Pause Unity
@Override protected void onPause()
{
super.onPause();
MultiWindowSupport.saveMultiWindowMode(this);
if (MultiWindowSupport.getAllowResizableWindow(this))
return;
mUnityPlayer.pause();
}
// Resume Unity
@Override protected void onResume()
{
super.onResume();
if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this))
return;
mUnityPlayer.resume();
}
// Low Memory Unity
@Override public void onLowMemory()
{
super.onLowMemory();
mUnityPlayer.lowMemory();
}
// Trim Memory Unity
@Override public void onTrimMemory(int level)
{
super.onTrimMemory(level);
if (level == TRIM_MEMORY_RUNNING_CRITICAL)
{
mUnityPlayer.lowMemory();
}
}
// This ensures the layout will be correct.
@Override public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
mUnityPlayer.configurationChanged(newConfig);
}
// Notify Unity of the focus change.
@Override public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
mUnityPlayer.windowFocusChanged(hasFocus);
}
// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@SuppressLint("RestrictedApi")
@Override public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return mUnityPlayer.injectEvent(event);
return super.dispatchKeyEvent(event);
}
// Pass any events not handled by (unfocused) views straight to UnityPlayer
@Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
@Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
/*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
}

View File

@ -0,0 +1,62 @@
package com.jc.jcfw.security;
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import com.cege.games.release.R;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.crypto.Cipher;
public class BiometricHelper {
private static final String TAG = BiometricHelper.class.getSimpleName();
public static BiometricPrompt.PromptInfo createPromptInfo(Context context) {
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.prompt_info_title)) // e.g. "Sign in"
.setSubtitle(context.getString(R.string.prompt_info_subtitle)) // e.g. "Biometric for My App"
.setDescription(context.getString(R.string.prompt_info_description)) // e.g. "Confirm biometric to continue"
.setConfirmationRequired(false)
.setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
// .setDeviceCredentialAllowed(true) // Allow PIN/pattern/password authentication.
// Also note that setDeviceCredentialAllowed and setNegativeButtonText are
// incompatible so that if you uncomment one you must comment out the other
.build();
return promptInfo;
}
public static BiometricPrompt createBiometricPrompt(FragmentActivity activity, Consumer<Cipher> func) {
Executor executor = ContextCompat.getMainExecutor(activity.getApplicationContext());
return new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
Log.i(TAG, "Authentication error: " + errString);
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Log.i(TAG, "Authentication failed!");
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
Log.i(TAG, "Authentication succeeded!");
try {
func.accept(result.getCryptoObject().getCipher());
} catch (Exception e) {
Log.e("BiometricHelper", e.getMessage());
}
}
});
}
}

View File

@ -0,0 +1,27 @@
package com.jc.jcfw.security;
import javax.crypto.Cipher;
public interface CryptographyManager {
/**
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
* with the key. The secret key uses [ENCRYPT_MODE][Cipher.ENCRYPT_MODE] is used.
*/
Cipher getInitializedCipherForEncryption(String keyName);
/**
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
* with the key. The secret key uses [DECRYPT_MODE][Cipher.DECRYPT_MODE] is used.
*/
Cipher getInitializedCipherForDecryption(String keyName, byte[] initializationVector);
/**
* The Cipher created with [getInitializedCipherForEncryption] is used here
*/
EncryptedData encryptData(String plaintext, Cipher cipher);
/**
* The Cipher created with [getInitializedCipherForDecryption] is used here
*/
String decryptData(byte[] ciphertext, Cipher cipher);
}

View File

@ -0,0 +1,97 @@
package com.jc.jcfw.security;
import static android.security.keystore.KeyProperties.BLOCK_MODE_CBC;
import static android.security.keystore.KeyProperties.ENCRYPTION_PADDING_PKCS7;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
public class CryptographyManagerImpl implements CryptographyManager {
@Override
public Cipher getInitializedCipherForEncryption(String keyName) {
Cipher cipher;
try {
cipher = getCipher();
SecretKey secretKey = getOrCreateSecretKey(keyName);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize Cipher for encryption", e);
}
return cipher;
}
@Override
public Cipher getInitializedCipherForDecryption(String keyName, byte[] initializationVector) {
Cipher cipher;
try {
cipher = getCipher();
SecretKey secretKey = getOrCreateSecretKey(keyName);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(initializationVector));
} catch (Exception e) {
throw new RuntimeException("Failed to initialize Cipher for decryption", e);
}
return cipher;
}
@Override
public EncryptedData encryptData(String plaintext, Cipher cipher) {
try {
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(Charset.defaultCharset()));
return new EncryptedData(ciphertext, cipher.getIV());
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt data", e);
}
}
@Override
public String decryptData(byte[] ciphertext, Cipher cipher) {
try {
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt data", e);
}
}
private Cipher getCipher() throws Exception {
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ BLOCK_MODE_CBC + "/"
+ ENCRYPTION_PADDING_PKCS7);
}
private SecretKey getOrCreateSecretKey(String keyName) throws Exception {
String ANDROID_KEYSTORE = "AndroidKeyStore";
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null); // Keystore must be loaded before it can be accessed
SecretKey existingKey = (SecretKey) keyStore.getKey(keyName, null);
if (existingKey != null) {
return existingKey;
}
// if you reach here, then a new SecretKey must be generated for that keyName
KeyGenParameterSpec.Builder paramsBuilder = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
paramsBuilder.setBlockModes(BLOCK_MODE_CBC);
paramsBuilder.setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7);
int KEY_SIZE = 256;
paramsBuilder.setKeySize(KEY_SIZE);
paramsBuilder.setUserAuthenticationRequired(false);
paramsBuilder.setUserAuthenticationValidityDurationSeconds(30);
KeyGenParameterSpec keyGenParams = paramsBuilder.build();
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
ANDROID_KEYSTORE);
keyGenerator.init(keyGenParams);
return keyGenerator.generateKey();
}
}

View File

@ -0,0 +1,19 @@
package com.jc.jcfw.security;
public class EncryptedData {
private final byte[] ciphertext;
private final byte[] initializationVector;
public EncryptedData(byte[] ciphertext, byte[] initializationVector) {
this.ciphertext = ciphertext;
this.initializationVector = initializationVector;
}
public byte[] getCiphertext() {
return ciphertext;
}
public byte[] getInitializationVector() {
return initializationVector;
}
}

View File

@ -0,0 +1,8 @@
<?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="horizontal">
</LinearLayout>

View File

@ -9,4 +9,8 @@
<string name="facebook_app_id" translatable="false">1204701000119770</string> <string name="facebook_app_id" translatable="false">1204701000119770</string>
<string name="fb_login_protocol_scheme" translatable="false">fb1204701000119770</string> <string name="fb_login_protocol_scheme" translatable="false">fb1204701000119770</string>
<string name="facebook_client_token" translatable="false">3e29f2606ae15a99bb3824d2ef1a9d0b</string> <string name="facebook_client_token" translatable="false">3e29f2606ae15a99bb3824d2ef1a9d0b</string>
<string name="prompt_info_title" translatable="false">Biometric login for Wallet</string>
<string name="prompt_info_subtitle" translatable="false">Login using your biometric credential</string>
<string name="prompt_info_description" translatable="false">Confirm biometric to continue</string>
</resources> </resources>

View File

@ -6,6 +6,21 @@
</style> </style>
<style name="BaseUnityTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen"> <style name="BaseUnityTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen">
</style> </style>
<style name="AppCompatTranslucent" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
</style>
<style name="CebgMainActivity" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@android:color/black</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="WebViewTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen"> <style name="WebViewTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="colorPrimary">@android:color/black</item> <item name="colorPrimary">@android:color/black</item>