SO파일에 문자열을 숨기고 암호화키로 사용할 경우 감출 수 있는지 테스트 확인하기 위하여

기본 프로젝트를 이용하여 간단히 만들어 보도록 하겠습니다.

 

참고 URL

https://developer.android.com/studio/projects/add-native-code?hl=ko

 

프로젝트 생성

1. new project -> Native c++ 디폴트 프로젝트 생성

 

 

2. 빌드후 실행

 

 

3. 정상적으로 호출되는 것을 확인했고 소스코드를 확인해 보겠습니다.

생성한 프로젝트의 cpp>native-lib.cpp stringFromJNI메소드명을 사용할 Java_패키지_액티비티_stringFromJNI으로 이름 메소드 변경필요

extern "C" JNIEXPORT jstring JNICALL 
Java_패키지_액티비티_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

액티비티에서 사용 소스

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // Example of a call to a native method
    findViewById<TextView>(R.id.sample_text).text = stringFromJNI()
}
external fun stringFromJNI(): String
companion object {
    init {
        System.loadLibrary("native-lib")
    }
}

 

so파일 export

1. Build > Build Bundles(s) / APK(s) > Build APK(s)를 선택합니다.

2. Build > Analyze APK를 선택합니다.

3. apk압축 해제후 lib하위 폴더에 so파일이 생성된 것을 확인할 수 있습니다.

 

 

 

1. 다른 방법으로

https://developer.android.com/ndk/guides/cmake?hl=ko

toolbar gradle 명령에서 externalNativeBuild를 실행하면

 

 

로그에 so 파일 생성되고 경로가 정보창으로

...

app/build/intermediates/cmake/release/obj/x86_64/libnative-lib.so

...

으로 생성된 것을 확인할 수 있습니다.

 

다른 프로젝트에서 사용하기

so 폴더 파일들을 사용할 프로젝트에 jniLibs폴더 생성 모두 복사

Timber.tag(TAG).i("jni : " + stringFromJNI())
external fun stringFromJNI(): String

companion object {
    // Used to load the 'native-lib' library on application startup.
    init {
        System.loadLibrary("native-lib")
    }
}

 

로그로 잘 찍히는 것을 확인하였습니다.

 

 

 

SO 파일 HEX 값 확인

이와 같이 테스트한 목적은 암호화키를 하드 코딩을 숨길 수 있을까하는 관점에서 테스트해보았으며

hex editor에 문자열이 노출되는 것을 확인되어 파악이 쉽진않겠지만 보안이 보장되지 않는것을 확인하였습니다.

 

 

이상입니다.

프로미스에 대해서 사용해보겠습니다. 

자세한 설명은 하기 URL을 참고 하시기 바랍니다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

프로미스는 비동기 작업에 대해서 완료 또는 실패를 결과를 콜백을 전달하는 대신, 콜백을 첨부하는 방식의 객체입니다.

 

Promise

기본동작 예제  

  1. asyn를 원하는 작업에 대해서 Promise로 감싸고 파라미터로 전달된 resolve or reject로 성공 실패를 호출합니다.

  2. 결과를 return을 받으면 then, catch구문을 이용하여 결과, 에러에 대해서 처리를 할 수 있습니다.  

Promise를 여러게 처리하기 위해서  

하기와 같이 Promise.All을 이용하여 처리를 할 수 있습니다.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});


Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

 

Promise 흐름은 다음과 같습니다.

 

async / await 비동기처리

또다른 방법으로 async / await를 사용할 수 있습니다.

async function 선언은 AsyncFunction객체를 반환하는 하나의 비동기 함수를 정의합니다

 

기본 동작 

기존에 만들어 놓은 asyncFunction을 호출 

async function asyncCall() {
    console.log('calling’);
    try {
        const result = await asyncFunction();
        console.log("asyncCall : " + result);
    } catch(err) {
        //error 
        console.log(“error : " + err);
    }
    // expected output: "resolved"
}

내부적으로 async 함수는 항상 promise를 반환합니다. 만약 async 함수의 반환값이 명시적으로 promise가 아니라면 암묵적으로 promise로 감싸집니다.

 

async function foo() {
    return 1
}

위 코드는 아래와 같습니다.
function foo() {
    return Promise.resolve(1)
}
async function foo() {
    await 1
}

위 코드는 아래와 같습니다.
function foo() {
    return Promise.resolve(1).then(() => undefined)
}

위와 같이 비동기 처리에 대해서 사용해 보았습니다. 

기존에 startActivityForResult() 및 onActivityResult() 를 통해서 결과를 받았던 것을 Activity Result API는 시스템에서 전달되면 결과를 등록, 실행 및 처리하기 위한 구성요소를 제공합니다.

 

참고 URL

https://developer.android.com/training/basics/intents/result?hl=ko

 

활동으로부터 결과 가져오기  |  Android 개발자  |  Android Developers

개발자 앱 내의 활동이든 다른 앱의 활동이든 다른 활동을 시작하는 것이 단방향 작업일 필요는 없습니다. 다른 활동을 시작하고 다시 결과를 받을 수도 있습니다. 예를 들어 앱에서 카메라 앱

developer.android.com

사용해보기 

 

1. gradle 설정 

implementation 'androidx.activity:activity-ktx:1.2.0-beta01'
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'

2. ActivityResultLauncher 생성 

@RequiresApi(Build.VERSION_CODES.M)
private val singlePermissions = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) {  isSuccess ->
    Log.d(TAG, "isSuccess : " + isSuccess)
}

registerForActivityResult 파라미터로 ActivityResultContracts, 과 ActivityResultCallback를 넘기게 됩니다. 

 

3. 실행 

singlePermissions.launch(permissionName)

 

4. 2번의 결과 로그 확인 

정상적으로 동작하는것을 확인해 보았습니다. 

 

동작확인

@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
        @NonNull ActivityResultContract<I, O> contract,
        @NonNull ActivityResultCallback<O> callback) {
    return registerForActivityResult(contract, mActivityResultRegistry, callback);

1. 위 registerForActivityResult를 통해서  ActivityResultLauncher를 생성한 인스턴스를 실행시키는 코드로 ActivityResultLauncher를 생성합니다.

2. 생성시 첫번재 파라미터로 전달된 RequestPermission는 권한요청을 위해  ActivityResultContract를 상속받아 구현되어 제공되고있는 클래스로 하기와 같이 구현되어 있습니다.

  간단히 보면 createIntent메소드에 permission을 전달받고 parseResult로 결과를 제공합니다.

 

 

3. 하기 FragmentActivity에서onRequestPermissionsResult 오버라이딩메소드에서 조건 검사를 통해서 등록이 되어있으면 doDispatch의 callback.onActivityResult(contract.parseResult(resultCode, data))를 통해서 전달이 되어 두번째 파라미터로 전달된 callback메소드로 결과를 받을 수 있습니다.

@CallSuper
    @Override
    @Deprecated
    public void onRequestPermissionsResult(
            int requestCode,
            @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        if (!mActivityResultRegistry.dispatchResult(requestCode, Activity.RESULT_OK, new Intent()
                .putExtra(EXTRA_PERMISSIONS, permissions)
                .putExtra(EXTRA_PERMISSION_GRANT_RESULTS, grantResults))) {
            if (Build.VERSION.SDK_INT >= 23) {
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    }


private <O> void doDispatch(String key, int resultCode, @Nullable Intent data,
        @Nullable CallbackAndContract<O> callbackAndContract) {
    if (callbackAndContract != null && callbackAndContract.mCallback != null) {
        ActivityResultCallback<O> callback = callbackAndContract.mCallback;
        ActivityResultContract<?, O> contract = callbackAndContract.mContract;
        callback.onActivityResult(contract.parseResult(resultCode, data));
    } else {
        mPendingResults.putParcelable(key, new ActivityResult(resultCode, data));
    }
}

 

간단히 사용해보았는데 기존에 onActivityResult를 오버라이딩해서 처리했던것을 제공된 api를 사용하여 하기 구글 예제와 같이 ActivityResultContract를 상속받아 구현을 하면 동작에 대한 요청과 결과의 소스관리과 명확해 질 것 같습니다.

class PickRingtone : ActivityResultContract<Int, Uri?>() {
        override fun createIntent(context: Context, ringtoneType: Int) =
            Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
                putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
            }

        override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
            if (resultCode != Activity.RESULT_OK) {
                return null
            }
            return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
        }
    }

 

+ Recent posts