Skip to content

Conversation

@HI-JIN2
Copy link
Member

@HI-JIN2 HI-JIN2 commented Feb 12, 2026

Summary

SecurePrefs KeyStoreException 해결 및 데이터 복구
KeyStoreException 발생시 앱이 계속 죽어서 실행조차 안 됨. @HI-JIN2도 DEV모드지만 동일한 현상을 겪었음

KeyStoreException (특히 "Signature/MAC verification failed") 발생 시 앱이 강제 종료되지 않고, 손상된 키나 데이터를 삭제한 후 재설정(Graceful Degradation)하여 앱을 정상적으로 사용할 수 있도록 합니다.

키 저장소 오류가 발생하면 저장된 모든 암호화된 데이터(로그인 토큰 등)가 초기화됩니다.

Describe your changes

As-is To-Be
KeyStoreException 발생시 앱이 계속 죽어서 실행조차 안 됨. 캐시 및 데이터 밀면 되는데 사용자는 이를 모름. KeyStoreException 발생시 자동으로EncryptedSharedPreferences를 삭제하고 다시 생성

KeyStoreException 또는 GeneralSecurityException 발생 시

  1. 에러를 로그(Timber.e) 및 Crashlytics(recordException)에 리포팅
  2. 기존의 손상된 SharedPreferences 파일(secure_prefs.xml)을 삭제
  3. MasterKey도 리셋을 시도(키 자체가 손상되었을 수 있음)
  4. EncryptedSharedPreferences를 다시 생성하여 반환

Issue

To reviewers

@HI-JIN2 HI-JIN2 requested a review from PeraSite February 12, 2026 05:10
@HI-JIN2 HI-JIN2 self-assigned this Feb 12, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @HI-JIN2, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 KeyStoreException으로 인해 앱이 비정상적으로 종료되는 문제를 해결하기 위한 중요한 업데이트입니다. 손상된 암호화된 데이터를 감지하고 자동으로 복구하는 메커니즘을 도입하여 사용자 경험을 개선하고 앱의 안정성을 높였습니다. 이 변경으로 인해 오류 발생 시 암호화된 사용자 데이터는 초기화되지만, 앱은 계속해서 정상적으로 작동할 수 있게 됩니다.

Highlights

  • KeyStoreException 처리: KeyStoreException 또는 GeneralSecurityException 발생 시 앱이 강제 종료되는 것을 방지하기 위해 예외 처리 로직이 추가되었습니다.
  • 데이터 복구 메커니즘: 손상된 EncryptedSharedPreferences 파일과 MasterKey를 삭제하고 재설정하여 앱이 정상적으로 작동하도록 하는 복구 로직이 구현되었습니다.
  • 로그인 토큰 초기화: 키 저장소 오류 발생 시 모든 암호화된 데이터(예: 로그인 토큰)가 초기화됩니다.
  • 크래시 리포팅 및 로깅: 예외 발생 시 Timber를 통해 로그를 기록하고 FirebaseCrashlytics에 리포팅하여 문제 진단에 도움을 줍니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • app/src/main/java/com/eatssu/android/di/SecurePrefsModule.kt
    • SecurePrefs 생성 시 발생할 수 있는 예외를 처리하기 위한 try-catch 블록을 추가했습니다.
    • 예외 발생 시 Timber를 이용한 로깅 및 FirebaseCrashlytics를 통한 예외 보고 기능을 추가했습니다.
    • 손상된 SharedPreferences 파일을 삭제하고 EncryptedSharedPreferences를 재설정하는 복구 로직을 구현했습니다.
    • 기존 SecurePrefs 생성 로직을 createSecurePrefs라는 private 함수로 분리하여 가독성과 재사용성을 높였습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 KeyStoreException 발생 시 앱이 강제 종료되는 문제를 해결하기 위해 SecurePrefs 초기화 로직을 개선하는 중요한 수정입니다. 예외 발생 시 데이터를 삭제하고 재시도하는 접근 방식은 올바르다고 생각합니다. 코드의 안정성과 유지보수성을 더욱 높이기 위해 몇 가지 제안 사항을 남깁니다. 주요 내용은 재시도 로직의 실패 케이스 처리, 더 구체적인 예외 처리, 그리고 상수를 사용한 코드 개선에 관한 것입니다.

Comment on lines +23 to +32
} catch (e: Exception) {
Timber.e(e, "Failed to create SecurePrefs. Resetting...")
FirebaseCrashlytics.getInstance().recordException(e)

// Clear data
context.deleteSharedPreferences("secure_prefs")

// Retry
createSecurePrefs(context)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

catch 블록에는 두 가지 잠재적인 문제가 있습니다.

  1. 재시도 실패 시 앱 충돌 (심각도: Critical): 31번째 줄의 createSecurePrefs(context) 재시도가 실패할 경우, 해당 예외가 처리되지 않아 앱이 충돌하게 됩니다. 이는 핫픽스의 주 목적인 '앱 충돌 방지'에 위배됩니다. 재시도 로직을 try-catch로 한번 더 감싸서, 두 번째 실패 시에는 명확한 예외를 던지도록 처리해야 합니다.

  2. 일반적인 Exception 처리 (심각도: High): KeyStoreException과 같은 특정 예외가 아닌 일반 Exception을 잡게 되면, 예상치 못한 다른 종류의 예외(예: NullPointerException)까지 이 복구 로직을 타게 되어 의도치 않은 데이터 삭제를 유발할 수 있습니다. when 절을 사용해 GeneralSecurityException이나 IOException 같은 예상된 예외에 대해서만 복구 로직을 수행하고, 그 외에는 예외를 다시 던져서 문제를 파악할 수 있도록 하는 것이 더 안전합니다.

아래는 이 두 가지 문제를 모두 수정한 제안 코드입니다. 참고로, 제안된 코드를 적용하려면 java.io.IOExceptionjava.security.GeneralSecurityException을 import해야 할 수 있습니다.

        } catch (e: Exception) {
            when (e) {
                is java.security.GeneralSecurityException, is java.io.IOException -> {
                    Timber.e(e, "Failed to create SecurePrefs. Resetting...")
                    FirebaseCrashlytics.getInstance().recordException(e)

                    // Clear data
                    context.deleteSharedPreferences("secure_prefs")

                    // Retry
                    try {
                        createSecurePrefs(context)
                    } catch (retryException: Exception) {
                        Timber.e(retryException, "Failed to create SecurePrefs even after reset.")
                        FirebaseCrashlytics.getInstance().recordException(retryException)
                        throw IllegalStateException("Could not initialize SecurePrefs after reset.", retryException)
                    }
                }

                else -> throw e
            }
        }

FirebaseCrashlytics.getInstance().recordException(e)

// Clear data
context.deleteSharedPreferences("secure_prefs")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

하드코딩된 문자열 "secure_prefs"가 28라인과 41라인 두 곳에서 사용되고 있습니다. 이 문자열을 object 내에 private const val로 선언하여 재사용하면 오타 발생 가능성을 줄이고 유지보수성을 높일 수 있습니다.

예시:

object SecurePrefsModule {
    private const val SECURE_PREFS_FILENAME = "secure_prefs"

    // ...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SecurePrefsModule.provideSecurePrefs - KeyStoreException

1 participant