반응형

이번 포스팅은 Android Build 오류에 대하여 알아보도록 하겠습니다.


< Error >


android.os.FileUriExposedException: file:///storage/emulated/0/Download/viewData.pdf exposed beyond app through ClipData.Item.getUri()


< Solution>

1. Android 7.0 이상부터 파일 공유 시 API 정책 변경

2. file:// URI 의 직접적인 노출이 아니라 content://URI 로 보내고 이에 대해서 임시 Access 권한을 부여하는 방식으로 변경이 되었습니다.

    아래는 Google Android OS 7.0 공식 문서 입니다.

   (링크 : https://developer.android.com/about/versions/nougat/android-7.0-changes.html#accessibility)


파일 시스템 권한 변경


개인 파일의 보안을 강화하기 위해, Android 7.0 이상을 대상으로 하는 앱의 개인 디렉터리는 액세스가 제한됩니다(0700). 이 설정은 크기 또는 존재 여부와 같은 개인 파일의 메타데이터 유출을 막아줍니다. 이러한 권한 변경은 여러 가지 부작용이 있습니다.

▶ 소유자가 개인 파일의 파일 권한을 더 이상 완화해서는 안 되며, MODE_WORLD_READABLE 및/또는 MODE_WORLD_WRITEABLE을 사용하여 권한을 완화하려고 시도하면 SecurityException이 트리거됩니다.

참고: 아직까지는 이 제한이 완전히 적용되지 않습니다. 앱이 여전히 기본 API 또는 File API를 사용하여 개인 디렉터리에 대한 권한을 수정할 수도 있습니다. 하지만 개인 디렉터리에 대한 권한은 부득이한 경우가 아니라면 완화하지 않는 것이 좋습니다.

▶ 패키지 도메인 외부에서 file:// URI를 전달하면 수신기가 액세스 불가능한 경로로 남아 있을 수 있습니다. 따라서 file:// URI를 전달하려고 시도하면 FileUriExposedException이 트리거됩니다. 개인 파일의 내용을 공유하기 위해 권장되는 방법은 FileProvider를 사용하는 것입니다.

 DownloadManager는 비공개로 저장된 파일을 더 이상 파일 이름별로 공유할 수 없습니다. 레거시 애플리케이션은 COLUMN_LOCAL_FILENAME에 액세스할 때 액세스가 불가능한 경로가 될 수 있습니다. Android 7.0 이상을 대상으로 하는 앱은 COLUMN_LOCAL_FILENAME에 액세스할 때 SecurityException을 트리거합니다. DownloadManager.Request.setDestinationInExternalFilesDir() 또는 DownloadManager.Request.setDestinationInExternalPublicDir()을 사용하여 다운로드위치를 공용 위치로 설정하는 레거시 애플리케이션은 COLUMN_LOCAL_FILENAME에 있는 경로에 여전히 액세스할 수 있지만, 이 메서드는 부득이한 경우가 아니라면 사용하지 않는 것이 좋습니다. DownloadManager에 의해 노출되는 파일에 액세스하는 좋은 방법은 ContentResolver.openFileDescriptor()를 사용하는 것입니다.


      앱 사이의 파일 공유


Android 7.0을 대상으로 하는 앱의 경우, Android 프레임워크는 앱 외부에서 file:// URI의 노출을 금지하는 StrictMode API 정책을 적용합니다. 파일 URI를 포함하는 인텐트가 앱을 떠나면 FileUriExposedException 예외와 함께 앱에 오류가 발생합니다.

애플리케이션 간에 파일을 공유하려면 content:// URI를 보내고 이 URI에 대해 임시 액세스 권한을 부여해야 합니다. 이 권한을 가장 쉽게 부여하는 방법은 FileProvider 클래스를 사용하는 방법입니다. 권한과 파일 공유에 대한 자세한 내용은 파일 공유를 참조하세요.


3. AndroidMenifext.xml를 수정하여 fileprovider를 지정합니다.



<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_path" />
</provider>


4.  res/xml/file_provider_path.xml 파일을 생성하여 아래와 같이 파일 경로를 지정합니다.


<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />

<files-path
name="files"
path="." />

</paths>


반응형
반응형

이번 포스팅은  Android Build 오류에 대하여 알아보도록 하겠습니다.


< Error >


java.security.KeyStoreException: entries cannot be protected with passwords

     at android.security.keystore.AndroidKeyStoreSpi.engineSetKeyEntry(AndroidKeyStoreSpi.java:308)

     at java.security.KeyStore.setKeyEntry(KeyStore.java:1179)


< Solution > 


setKeyEntry method가 분명히 존재하고 passwords 매개 변수를 사용한다는 사실에도 불구하고 AndroidKeystore 구현은 이를 지원하지 않습니다.

어떤 경우든 장치의 어느 곳에 저장되어 있는 강력한 암호든 약한 암호는 안전하지 않습니다. 이는 특히 소스 코드(일반적으로 소스 코드, not-android Context 포함)에 해당됩니다. 당신의 앱 APK를 손에 넣으면, 몇 가지 훌륭한 APK 분석 도구 중 하나를 사용하여 Class의 constants pool에 액세스할 수 있는데, 이 pool은 일반 텍스트로 되어 있을 것입니다. 아무리 난독화해도 이것을 막을 수 없었고, 심지어 더 어렵게 만들 수도 없습니다. 이것이 아마도 AndroidKeystore 구현이 이것을 허용하지 않는 이유일 것입니다.

안드로이드 장치의 키 저장소 서비스의 전체 포인트(실제로 Unix socket을 통해 대화할 수 있는 별도의 프로세스로 구현됨)는 사용자가 암호를 입력하여 중요한 자료에 대한 액세스를 보호하는 것이므로 Key 쌍이나 다른 비밀 암호를 입력하는 것은 의미가 없습니다. 더 최근의 스마트폰에서는, 이 모든 것이 Hardware에 내장되어 있어, 매우 강력한 보안을 제공합니다.

Apparently despite the fact that the method takes a password parameter, the AndroidKeystoreimplementation does not support this, take a look at line 200 in the source.

In any case, a password, whether strong or weak that is stored anywhere on the device is unsafe. This is especially true of source code (including source code in general, non-android contexts). If I get my hands on your app's APK, I can use one of several excellent APK analysis tools to access the class's constant pool, which will contain the password in plain text. No amount of obfuscation could prevent this, or even make it more difficult. This is perhaps the reason that the AndroidKeystoreimplementation doesn't allow this.

The whole point of the key store service on android device (it's actually implemented as a separate process you can talk to via a unix socket) is to protect access to sensitive material by requiring a users passcode to unlock it, so there's no point in putting a password on your key pairs or other secrets.. In more recent phones, this is all implmented in hardware, which provides very strong security.

참조 링크 : https://stackoverflow.com/questions/27575059/android-entries-cannot-be-protected-with-passwords

반응형
반응형

이번 포스팅은 Android에서 보다 더 안전하게 데이터를 저장하는 방법에 대해 알아보도록 하겠습니다.

 

Android Jetpack 일부인 Security Library유휴 상태의 데이터를 읽고 쓰는 것과 관련된 보안 모범 사례키 생성 및 검증의 구현을 위해 제공합니다.
라이브러리는 빌더 패턴을 사용하여 다음 보안 레벨에 안전한 기본 설정을 제공합니다.

강력한 암호화와 우수한 성능의 균형을 유지하는 강력한 보안 : 이 보안 수준은 뱅킹 및 채팅 앱과 같은 소비자 앱과 인증서 해지 확인을 수행하는 엔터프라이즈 앱에 적합합니다.

최대 보안 : 이 수준의 보안은 키 액세스를 제공하기 위해 하드웨어 기반 키 저장소 및 사용자 존재가 필요한 앱에 적합합니다. 이 안내서는 Security Library의 권장 보안 구성 SharedPreference 파일에 저장된 암호화된 데이터를 쉽고 안전하게 읽고 쓰는 방법을 보여줍니다.

 
키 관리Security Library는 키 관리를 위해 2부시스템을 사용합니다.
 
파일 또는 SharedPreference를 암호화하기위한 하나 이상의 Key키가 포함 Keyset 입니다. 키셋 자체는SharedPreferences에 저장됩니다.
 
모든 Keyset를 암호화 하는 마스터 키 : 이 키는 Android 키 저장소 시스템을 사용하여 저장됩니다 .

 

다음 소스 코드는 마스터 키를 정의하는 방법을 보여줍니다.

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

 

#. Library에 포함된 Class

 

Security Library에는 보다 안전한 데이터를 제공하기 위해 다음과 같은 클래스가 포함되어 있습니다.

EncryptedFile

FileInputStream  및 FileOutputStream을 사용자 정의 구현하여 앱에 보다 안전한 스트리밍 읽기 및 쓰기 작업을 제공합니다.

▶ 파일 스트림에서 안전한 읽기 및 쓰기 작업을 제공하기 위해 Security Library는 AEAD (Authenticated Encryption with Associated Data)를 기본을 사용합니다. GitHub GitHub의 Tink 라이브러리 설명서에서 이 기본 요소에 대해 자세히 알아보십시오..

EncryptedSharedPreferences

SharedPreferences  클래스를 감싸고 다음 두 가지 구성 방법을 사용하여 키와 값을 자동으로 암호화합니다.

▶ Key는 암호화되고 적절하게 조회될 수 있도록 결정론적 암호화 알고리즘을 사용하여 암호화합니다

타겟 TTS
/span>

 

▶ Value AES-256 GCM을 사용하여 암호화되며 결정적이지 않습니다.

다음 섹션에서는 이러한 클래스를 사용하여 파일 및 공유 환경 설정으로 공통 작업을 수행하는 방법을 보여줍니다.

 

#. 파일 읽기

 

음 소스 코드는 EncryptedFile을 사용하여 파일의 내용을 보다 안전하게 읽는 방법을 보여줍니다 .

val fileToRead = "my_sensitive_data.txt"
lateinit
var byteStream: ByteArrayOutputStream
val encryptedFile = EncryptedFile.Builder(
   
File(context.getFilesDir(), fileToRead),
    context
,
    masterKeyAlias
,
   
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

try {
    encryptedFile
.openFileInput().use { fileInputStream ->
       
try {
            byteStream
= ByteArrayOutputStream()
           
var nextByte = fileInputStream.read()
           
while (nextByte != -1) {
                byteStream
.write(nextByte)
                nextByte
= fileInputStream.read()
           
}

           
val fileContents = byteStream.toByteArray()

       
} catch (ex: Exception) {
           
// Error occurred opening raw file for reading.
       
} finally {
            fileInputStream
.close()
       
}
   
})
} catch (ex: IOException) {
   
// Error occurred opening encrypted file for reading.
}

 

#. 파일 쓰기

 

다음 소스 코드는 EncryptedFile을 사용하여 파일의 내용을 보다 안전하게 쓰는 방법을 보여줍니다.

val fileToWrite = "my_other_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
   
File(context.getFilesDir(), fileToWrite),
    context
,
    masterKeyAlias
,
   
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

// Write to a file.
try {
   
val outputStream: FileOutputStream? = encryptedFile.openFileOutput()
    outputStream
?.apply {
        write
("MY SUPER SECRET INFORMATION"
           
.toByteArray(Charset.forName("UTF-8")))
        flush
()
        close
()
   
}
} catch (ex: IOException) {
   
// Error occurred opening file for writing.
}

추가 보안이 필요한 사용 사례의 경우 다음 단계를 완료하십시오.

 KeyGenParameterSpec.Builder를 생성하십시오. Build 객체로 setUserAuthenticationRequired()를 true로 전달하고 0보다 큰 값을 setUserAuthenticationValidityDurationSeconds()로 전달하세요.

사용자에게 createConfirmDeviceCredentialIntent()을 사용하여 자격 증명을 입력하도록 키 사용을 위해 사용자 인증 요청하는 방법에 대해 자세히 알아보십시오 .

★ 참고 : 보안 라이브러리는 BiometricPrompt 암호화 작업 수준에서 지원하지 않습니다.

 확인된 자격 증명 콜백을 가져오기 위해 onActivityResult()를 재정의 하십시오. 자세한 정보 키 사용을 위한 사용자 인증 요구 참조하십시오.

 

#. SharedPreference 편집

 

다음 소스코드는 EncryptedSharedPreferences은 보다 안전한 방식으로 사용자의 SharedPerference를 편집하는 방법을 보여줍니다 .

val sharedPreferences = EncryptedSharedPreferences
   
.create(
    fileName
,
    masterKeyAlias
,
    context
,
   
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
   
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

val sharedPrefsEditor = sharedPreferences.edit()

 

Reference

1.  https://developer.android.com/topic/security/data

반응형

+ Recent posts