
рдореИрдВ рдЕрдХреНрд╕рд░ рд╕рд╡рд╛рд▓ рд╕реБрдирддрд╛ рд╣реВрдВ рдХрд┐ "рдПрдВрдбреНрд░реЙрдЗрдб рдРрдк рдореЗрдВ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреИрд╕реЗ рд▓рд╛рдЧреВ рдХрд░реЗрдВ?", "рдЬрд╣рд╛рдВ рдПрдХ рдкрд┐рди рд╕реНрдЯреЛрд░ рдХрд░рдирд╛ рд╣реИ?", "рдЕрд░реЗ рдпрд╛рд░, рдХреНрдпрд╛ рдореИрдВ рд╕реБрд░рдХреНрд╖рд┐рдд рд░рд╣реВрдВрдЧрд╛ рдЕрдЧрд░ рдореИрдВ рдЗрд╕ рддрд░рд╣ рд╕реЗ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рд╕реБрд╡рд┐рдзрд╛ рд▓рд╛рдЧреВ рдХрд░реВрдВ?" рдФрд░ рдмрд╣реБрдд рддрд░рд╣ рдХрд╛ред рдореИрдВ рдЗрди рд╕рд╡рд╛рд▓реЛрдВ рдХрд╛ рдЬрд╡рд╛рдм рджреЗрддреЗ рд╣реБрдП рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдердХ рдЧрдпрд╛ рдерд╛ рдЗрд╕рд▓рд┐рдП рдореИрдВрдиреЗ рд╕рднреА рдкреНрд░рд╢реНрдирдХрд░реНрддрд╛рдУрдВ рдХреЗ рд╕рд╛рде рд╕рд╛рдЭрд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЕрдкрдиреЗ рд╕рднреА рд╡рд┐рдЪрд╛рд░реЛрдВ рдХреЛ рдПрдХ рдмрд╛рд░ рд▓рд┐рдЦрдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ред
рд╕рд╛рдордЧреНрд░реА рдХреА рддрд╛рд▓рд┐рдХрд╛
рдкреНрд░рдорд╛рдгреАрдХрд░рдг: рдореБрдЭреЗ рдпрд╣ рдХреНрдпреЛрдВ рдХрд░рдирд╛ рд╣реИ?
рдЪрд▓рд┐рдП рдкрд░рд┐рднрд╛рд╖рд╛ рд╕реЗ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВред рдкреНрд░рдорд╛рдгреАрдХрд░рдг (рдЧреНрд░реАрдХ рд╕реЗ: ╬▒с╜Р╬╕╬╡╬╜ication╬╣ik рдкреНрд░рдорд╛рдгрд┐рдХреЛрд╕, "рд╡рд╛рд╕реНрддрд╡рд┐рдХ, рд╡рд╛рд╕реНрддрд╡рд┐рдХ", ╬▒es╬╜ "рдкреНрд░рд╛рдорд╛рдгрд┐рдХрддрд╛ рд╕реЗ," рд▓реЗрдЦрдХ ") рдПрдХ рдЬреЛрд░ рд╕рд╛рдмрд┐рдд рдХрд░рдиреЗ рдХреА рдХреНрд░рд┐рдпрд╛ рд╣реИ, рдЬреИрд╕реЗ рдХрд┐ рдХрдВрдкреНрдпреВрдЯрд░ рд╕рд┐рд╕реНрдЯрдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреА рдкрд╣рдЪрд╛рдиред
рдЗрд╕рд▓рд┐рдП, рдпрджрд┐ рдЖрдкрдХреЗ рдЖрд╡реЗрджрди рдореЗрдВ рд╕рдВрд╡реЗрджрдирд╢реАрд▓ рдЬрд╛рдирдХрд╛рд░реА рд╣реИ (рдХрд┐рд╕реА рднреА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреА рдЬрд╛рдирдХрд╛рд░реА рд╕рдВрд╡реЗрджрдирд╢реАрд▓ IMHO рд╣реИ) рддреЛ рдЖрдкрдХреЛ рдЗрд╕ рдЬрд╛рдирдХрд╛рд░реА рдХреЗ рдЕрдирдзрд┐рдХреГрдд рдЙрдкрдпреЛрдЧ рдХреЛ рд░реЛрдХрдиреЗ рдХреЗ рд▓рд┐рдП рдРрдк рдореЗрдВ рдПрдХ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкрд░рд┐рджреГрд╢реНрдп рдЬреЛрдбрд╝рдирд╛ рд╣реЛрдЧрд╛ред
рд╕рдмрд╕реЗ рд▓реЛрдХрдкреНрд░рд┐рдп рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкрд░рд┐рджреГрд╢реНрдп рдЗрд╕ рдкреНрд░рдХрд╛рд░ рд╣реИрдВ:
- рд▓реЙрдЧрд┐рди + рдкрд╛рд╕рд╡рд░реНрдб
- рдорд╛рд╕реНрдЯрд░ рдкрд╛рд╕рд╡рд░реНрдб
- рдкрд┐рди (4 рдпрд╛ рдЕрдзрд┐рдХ рдЕрдВрдХ)
- рдмреЙрдпреЛрдореЗрдЯреНрд░рд┐рдХреНрд╕
рд╕реНрд╡рд╛рднрд╛рд╡рд┐рдХ рд░реВрдк рд╕реЗ, рд▓реЙрдЧрд┐рди рдФрд░ рдкрд╛рд╕рд╡рд░реНрдб рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдПрдХ рдмреИрдХ-рдПрдВрдб рд╕реЗ рдЖрдкрдХреЗ рдЖрд╡реЗрджрди рдкрд░ рдЖрддрд╛ рд╣реИ рдФрд░ рдЗрд╕ рддрдВрддреНрд░ рдХреА рд╕реБрд░рдХреНрд╖рд╛ рд╣рдо рдмреИрдХ-рдПрдВрдб рд╕реБрд░рдХреНрд╖рд╛ рдЖрд╢реНрд╡рд╛рд╕рди рдЯреАрдо рдХреЛ рдЫреЛрдбрд╝ рджреЗрдВрдЧреЗ;) рдмрд╕ рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рдХреБрдВрдЬреА рдкрд┐рдирд┐рдВрдЧ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдордд рднреВрд▓рдирд╛ред
рдорд╛рд╕реНрдЯрд░ рдкрд╛рд╕рд╡рд░реНрдб рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рд╢рд╛рдпрдж рд╣реА рдХрднреА рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдХреЗрд╡рд▓ рдЙрди рдРрдкреНрд╕ рдореЗрдВ рдЬрд┐рдиреНрд╣реЗрдВ рдЙрдЪреНрдЪ рд╕реНрддрд░ рдХреА рд╕реБрд░рдХреНрд╖рд╛ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ (рдЬреИрд╕реЗ рдкрд╛рд╕рд╡рд░реНрдб рдкреНрд░рдмрдВрдзрдХ)ред
рдЗрд╕ рдкреНрд░рдХрд╛рд░, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдХреЗрд╡рд▓ рджреЛ рд╕рдмрд╕реЗ рд▓реЛрдХрдкреНрд░рд┐рдп рдкрд░рд┐рджреГрд╢реНрдп рд╣реИрдВ: рдПрдХ рдкрд┐рди рдФрд░ рдмрд╛рдпреЛрдореЗрдЯреНрд░рд┐рдХреНрд╕ ред рд╡реЗ рдХрд╛рдлреА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдЕрдиреБрдХреВрд▓ рд╣реИрдВ рдФрд░ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рдЕрдкреЗрдХреНрд╖рд╛рдХреГрдд рдЖрд╕рд╛рди рд╣реИрдВ (рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рд╡реЗ рдирд╣реАрдВ рд╣реИрдВ ...)ред рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рд╣рдо рдЗрди рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдХреЗ рд╕рд╣реА рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рдореБрдЦреНрдп рдкрд╣рд▓реБрдУрдВ рдХреЛ рд╢рд╛рдорд┐рд▓ рдХрд░реЗрдВрдЧреЗред
рд╕рд░рд▓ рддрд░реАрдХрд╛ рд╣реИ
рдЬрд░рд╛ рд╕реЛрдЪрд┐рдП, рдЖрдк рдПрдХ Android рдбреЗрд╡рд▓рдкрд░ рд╣реИрдВ рдФрд░ рдЖрдкрдХрд╛ рдХреЛрдб рдЖрдкрдХреЗ рдкреИрд╕реЗ рдЫрд╛рдкрддрд╛ рд╣реИред рдЖрдк рдХрд┐рд╕реА рднреА рдЪреАрдЬ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЪрд┐рдВрддрд╛ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рдЖрдкрдХреЛ рдЧрдВрднреАрд░ рдореЛрдмрд╛рдЗрд▓ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реБрд░рдХреНрд╖рд╛ рд╡рд┐рд╢реЗрд╖рдЬреНрдЮрддрд╛ рдореЗрдВ рдмрд╣реБрдд рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИред рд▓реЗрдХрд┐рди рдПрдХ рджрд┐рди, рдПрдХ рдкреНрд░рдмрдВрдзрдХ рдЖрдкрдХреЗ рдкрд╛рд╕ рдЖрддрд╛ рд╣реИ рдФрд░ "рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдореЗрдВ рдПрдХ рдкрд┐рди рдФрд░ рдПрдХ рдлрд┐рдВрдЧрд░рдкреНрд░рд┐рдВрдЯ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдПрдХ рдЕрддрд┐рд░рд┐рдХреНрдд рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ" рдХреЗ рд▓рд┐рдП рдПрдХ рдХрд╛рд░реНрдп рджреЗрддрд╛ рд╣реИред рдХрд╣рд╛рдиреА рдпрд╣рд╛рдБ рд╕реЗ рд╢реБрд░реВ рд╣реЛрддреА рд╣реИ ...
рдкрд┐рди рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдк рдХреБрдЫ рд╕реНрдХреНрд░реАрди рдЗрд╕ рддрд░рд╣ рдмрдирд╛рдПрдВрдЧреЗ:


рдФрд░ рдЕрдкрдирд╛ рдкрд┐рди рдмрдирд╛рдиреЗ рдФрд░ рдЬрд╛рдВрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рдРрд╕рд╛ рдХреЛрдб рд▓рд┐рдЦреЗрдВ
fun savePin(pin: String) { preferences.edit().putString(StorageKey.PIN, pin).apply() }
fun authenticate(pin: String) { authenticationState.value = if (pinIsValid(pin)) { AuthenticationState.AUTHENTICATED } else { AuthenticationState.INVALID_AUTHENTICATION } } private fun pinIsValid(pin: String): Boolean { return preferences.getString(StorageKey.PIN, null) == pin }
рдмрд╕ рдЗрддрдирд╛ рд╣реА! рдЕрдм, рдЖрдкрдХреЗ рдкрд╛рд╕ рдПрдХ рдкрд┐рди рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдПрдХ рд╢рд╛рдВрдд рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкреНрд░рдгрд╛рд▓реА рд╣реИред рдмрдзрд╛рдИ рд╣реЛред рдпрд╣ рдмрд╣реБрдд рдЖрд╕рд╛рди рдерд╛, рд╣реИ рдирд╛?
рдмреЗрд╢рдХ, рдЖрдкрдиреЗ рдкрд╣рд▓реЗ рд╣реА рдореЗрд░реЗ рд╢рдмреНрджреЛрдВ рдореЗрдВ рд╡рд┐рдбрдВрдмрдирд╛ рдХреЛ рдкрдХрдбрд╝ рд▓рд┐рдпрд╛ рд╣реИред рдпрд╣ рддрд░реАрдХрд╛ рдмрд╣реБрдд рдмреБрд░рд╛ рд╣реИ рдХреНрдпреЛрдВрдХрд┐ рдПрдХ рдкрд┐рди рдХреЛ рдкреНрд▓реЗрдирдЯреЗрдХреНрд╕реНрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдЕрдЧрд░ рдорд╛рд▓рд╡реЗрдпрд░ рдХрд┐рд╕реА рддрд░рд╣ рд╕реЗ рдЗрдВрдЯрд░рдирд▓ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реНрдЯреЛрд░реЗрдЬ рддрдХ рдкрд╣реБрдВрдЪ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдЙрд╕реЗ рдпреВрдЬрд░ рдкрд┐рди рдорд┐рд▓реЗрдЧрд╛ред рдЖрдк рдореБрдЭрд╕реЗ рдкреВрдЫ рд╕рдХрддреЗ рд╣реИрдВ "рдпрд╣ рдЗрддрдирд╛ рдмреБрд░рд╛ рдХреНрдпреЛрдВ рд╣реИ? рдпрд╣ рд╕реНрдерд╛рдиреАрдп рдкреНрд░рдорд╛рдгреАрдХрд░рдг рд╕реЗ рд╕рд┐рд░реНрдл рдПрдХ рдкрд┐рди рд╣реИ ..."ред рд╣рд╛рдБ, рд▓реЗрдХрд┐рди рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╣рд░ рдЬрдЧрд╣ рдПрдХ рд╣реА рдкрд┐рди рд╕реЗрдЯ рдХрд░рддреЗ рд╣реИрдВред рдЗрд╕рд▓рд┐рдП, рдПрдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкрд┐рди рдХрд╛ рдЬреНрдЮрд╛рди рдПрдХ рдШреБрд╕рдкреИрдард┐рдпрд╛ рдХреЛ рд╣рдорд▓реЗ рдХреА рд╕рддрд╣ рдХрд╛ рд╡рд┐рд╕реНрддрд╛рд░ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред
рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдРрд╕реА рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдпреЛрдЬрдирд╛ рдЖрдкрдХреЛ рдПрдХ рд╕реБрд░рдХреНрд╖рд┐рдд рддрд░реАрдХреЗ рд╕реЗ рдкрд┐рди рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рдирд╣реАрдВ рджреЗрддреА рд╣реИ (рд╣рдо рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдж рдореЗрдВ рдмрд╛рдд рдХрд░реЗрдВрдЧреЗ)ред
рдЪрд▓реЛ рдЗрд╕реЗ рдмреЗрд╣рддрд░ рдмрдирд╛рддреЗ рд╣реИрдВ
рд╣рдо рдЕрдкрдиреЗ рдкрд┐рдЫрд▓реЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рдХреИрд╕реЗ рд╕реБрдзрд╛рд░ рд╕рдХрддреЗ рд╣реИрдВ? рдкрд╣рд▓рд╛ рдФрд░ рд╕реНрдкрд╖реНрдЯ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдЖрдкрдХреЗ рдкрд┐рди рд╕реЗ рдПрдХ рд╣реИрд╢ рд▓реЗ рд░рд╣рд╛ рд╣реИ рдФрд░ рдЗрд╕ рд╣реИрд╢ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░ рд░рд╣рд╛ рд╣реИред
рд╣реИрд╢ рдлрд╝рдВрдХреНрд╢рди рдХреЛрдИ рднреА рдлрд╝рдВрдХреНрд╢рди рд╣реИ рдЬрд┐рд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдирд┐рд╢реНрдЪрд┐рдд рдЖрдХрд╛рд░ рдХреЗ рдорд╛рдиреЛрдВ рдХреЗ рдордирдорд╛рдиреЗ рдЖрдХрд╛рд░ рдХреЗ рдбреЗрдЯрд╛ рдХреЛ рдореИрдк рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рд╣реИрд╢ рдлрд╝рдВрдХреНрд╢рди рджреНрд╡рд╛рд░рд╛ рд▓реМрдЯрд╛рдП рдЧрдП рдорд╛рдиреЛрдВ рдХреЛ рд╣реИрд╢ рдорд╛рди, рд╣реИрд╢ рдХреЛрдб, рдбрд╛рдЗрдЬреЗрд╕реНрдЯ рдпрд╛ рдмрд╕ рд╣реИрд╢ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИред рдорд╛рди рдХрд╛ рдЙрдкрдпреЛрдЧ рд╣реИрд╢ рддрд╛рд▓рд┐рдХрд╛ рдирд╛рдордХ рдирд┐рд╢реНрдЪрд┐рдд рдЖрдХрд╛рд░ рдХреА рддрд╛рд▓рд┐рдХрд╛ рдХреЛ рдЕрдиреБрдХреНрд░рдорд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рд╣реИрд╢ рдЯреЗрдмрд▓ рдХреЛ рдЗрдВрдбреЗрдХреНрд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИрд╢ рдлрдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рд╣реИрд╢рд┐рдВрдЧ рдпрд╛ рд╕реНрдХреИрдЯрд░ рд╕реНрдЯреЛрд░реЗрдЬ рдПрдбреНрд░реЗрд╕рд┐рдВрдЧ рдХрд╣рд▓рд╛рддрд╛ рд╣реИред
рдПрдВрдбреНрд░реЙрдЗрдб рдлреНрд░реЗрдорд╡рд░реНрдХ ( рдЬрд╛рд╡рд╛ рдХреНрд░рд┐рдкреНрдЯреЛрдЧреНрд░рд╛рдлреА рдЖрд░реНрдХрд┐рдЯреЗрдХреНрдЪрд░ рдореЗрдВ , рд╕рдЯреАрдХ рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП) рдореЗрдВ рдмрд╣реБрдд рд╕рд╛рд░реЗ рдЙрдкрд▓рдмреНрдз рд╣реИрд╢ рдлрд╝рдВрдХреНрд╢рди рд╣реИрдВ, рд▓реЗрдХрд┐рди рдЖрдЬ рдЙрдирдореЗрдВ рд╕реЗ рдкреНрд░рддреНрдпреЗрдХ рдХреЛ рд╕реБрд░рдХреНрд╖рд┐рдд рдирд╣реАрдВ рдорд╛рдирд╛ рдЬрд╛рддрд╛ рд╣реИред рдореИрдВ рдЯрдХрд░рд╛рд╡ рдХреЗ рдХрд╛рд░рдг MD5 рдФрд░ SHA-1 рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рд╕рд▓рд╛рд╣ рдирд╣реАрдВ рджреЗрддрд╛ред SHA-256 рдЕрдзрд┐рдХрд╛рдВрд╢ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрдЪреНрдЫрд╛ рд╡рд┐рдХрд▓реНрдк рд╣реИред
fun sha256(byteArray: ByteArray): ByteArray { val digest = try { MessageDigest.getInstance("SHA-256") } catch (e: NoSuchAlgorithmException) { MessageDigest.getInstance("SHA") } return with(digest) { update(byteArray) digest() } }
рд╣реИрд╢ рдкрд┐рди рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реА savePin(...)
рд╡рд┐рдзрд┐ рдХреЛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░реЗрдВ
fun savePin(pin: String) { val hashedPin = sha256(pin.toByteArray()) val encodedHash = Base64.encodeToString(hashedPin, Base64.DEFAULT) preferences.edit().putString(StorageKey.PIN, encodedHash).apply() }
рд╣реИрд╢ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдПрдХ рдЕрдЪреНрдЫреА рд╢реБрд░реБрдЖрдд рд╣реИ, рд▓реЗрдХрд┐рди рдирдВрдЧреЗ рд╣реИрд╢ рд╣рдорд╛рд░реЗ рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рдирд╣реАрдВ рд╣реИред рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдЬреАрд╡рди рдореЗрдВ рдПрдХ рд╣рдорд▓рд╛рд╡рд░ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╕рднреА 4-рдЕрдВрдХреАрдп рдкрд┐рди рд╣реИрд╢ рдХреА рдЧрдгрдирд╛ рдХрд░ рдЪреБрдХрд╛ рд╣реИред рд╡рд╣ рдЙрди рд╕рднреА рдЪреЛрд░реА рдХрд┐рдП рдЧрдП рд╣реИрд╢рдб рдкрд┐рди рдХреЛ рдЖрд╕рд╛рдиреА рд╕реЗ рдбрд┐рдХреНрд░рд┐рдкреНрдЯ рдХрд░ рд╕рдХреЗрдЧрд╛ред рдЗрд╕рд╕реЗ рдирд┐рдкрдЯрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рджреГрд╖реНрдЯрд┐рдХреЛрдг рд╣реИ - рдПрдХ рдирдордХ ред
рдХреНрд░рд┐рдкреНрдЯреЛрдЧреНрд░рд╛рдлреА рдореЗрдВ, рдПрдХ рдирдордХ рдпрд╛рджреГрдЪреНрдЫрд┐рдХ рдбреЗрдЯрд╛ рд╣реЛрддрд╛ рд╣реИ рдЬреЛ рдПрдХ рддрд░рд╣ рдХреЗ рдлрд╝рдВрдХреНрд╢рди рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрддрд┐рд░рд┐рдХреНрдд рдЗрдирдкреБрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬреЛ "рд╣реИрд╢" рдбреЗрдЯрд╛, рдПрдХ рдкрд╛рд╕рд╡рд░реНрдб рдпрд╛ рдкрд╛рд╕рдлрд╝реНрд░реЗрдЬрд╝ред рднрдВрдбрд╛рд░рдг рдореЗрдВ рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ рд╕реБрд░рдХреНрд╖рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд▓рд╛рдЯреНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдРрддрд┐рд╣рд╛рд╕рд┐рдХ рд░реВрдк рд╕реЗ рдПрдХ рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ рдПрдХ рд╕рд┐рд╕реНрдЯрдо рдкрд░ рдкреНрд▓реЗрдирдЯреЗрдХреНрд╕реНрдЯ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рд▓реЗрдХрд┐рди рд╕рдордп рдХреЗ рд╕рд╛рде рдЕрддрд┐рд░рд┐рдХреНрдд рд╕реБрд░рдХреНрд╖рд╛ рдЙрдкрд╛рдпреЛрдВ рдХреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ рд╕рд┐рд╕реНрдЯрдо рд╕реЗ рдкрдврд╝реЗ рдЬрд╛рдиреЗ рд╕реЗ рдмрдЪрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╡рд┐рдХрд╕рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ред рдПрдХ рдирдордХ рдЙрди рддрд░реАрдХреЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ рд╣реИред
рдЕрдкрдиреЗ рд╕реБрд░рдХреНрд╖рд╛ рддрдВрддреНрд░ рдореЗрдВ рдирдордХ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдореЗрдВ рдКрдкрд░ рджрд┐рдЦрд╛рдП рдЧрдП рдХреЛрдб рдХреЛ рдЗрд╕ рддрд░рд╣ рдмрджрд▓рдирд╛ рд╣реЛрдЧрд╛
fun generate(lengthByte: Int = 32): ByteArray { val random = SecureRandom() val salt = ByteArray(lengthByte) random.nextBytes(salt) return salt }
fun savePin(pin: String) { val salt = Salt.generate() val saltedPin = pin.toByteArray() + salt val hashedPin = Sha256.hash(saltedPin) val encodedHash = Base64.encodeToString(hashedPin, Base64.DEFAULT) val encodedSalt = Base64.encodeToString(salt, Base64.DEFAULT) preferences.edit() .putString(StorageKey.PIN, encodedHash) .putString(StorageKey.SALT, encodedSalt) .apply() }
рдзреНрдпрд╛рди рджреЗрдВ, рдЖрдкрдХреЛ рдирдордХ рдХреЛ рдПрдХ рд╕рд╛рде рдкрд┐рди рдХреЗ рд╕рд╛рде рд╕реНрдЯреЛрд░ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ рдХреНрдпреЛрдВрдХрд┐ рдЖрдкрдХреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЗрдирдкреБрдЯ рд╕реЗ рдкрд┐рди рдХреА рдЬрд╛рдВрдЪ рдХрд░рддреЗ рд╕рдордп рд╣рд░ рдмрд╛рд░ рдкрд░рд┐рдгрд╛рдо рд╣реИрд╢ (рдирдордХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛) рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИред
private fun pinIsValid(pin: String): Boolean { val encodedSalt = preferences.getString(StorageKey.SALT, null) val encodedHashedPin = preferences.getString(StorageKey.PIN, null) val salt = Base64.decode(encodedSalt, Base64.DEFAULT) val storedHashedPin = Base64.decode(encodedHashedPin, Base64.DEFAULT) val enteredHashedPin = Sha256.hash(pin.toByteArray() + salt) return storedHashedPin contentEquals enteredHashedPin }
рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдХреЛрдб рдЕрднреА рднреА рд╕рдордЭрдиреЗ рдореЗрдВ рдЗрддрдирд╛ рдХрдард┐рди рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕ рд╕рдорд╛рдзрд╛рди рдХреА рд╕реБрд░рдХреНрд╖рд╛ рдмрд╣реБрдд рдордЬрдмреВрдд рд╣реЛ рдЧрдИ рд╣реИред рдореИрдВ рдФрд░ рднреА рдЕрдзрд┐рдХ рдХрд╣реВрдВрдЧрд╛, рдпрд╣ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдЕрдзрд┐рдХрд╛рдВрд╢ рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдХрд╛рдлреА рдЙрддреНрдкрд╛рджрди рдХреЗ рд▓рд┐рдП рддреИрдпрд╛рд░ рд╣реИ рдЬрд┐рдиреНрд╣реЗрдВ рдЙрдЪреНрдЪ рд╕реНрддрд░ рдХреА рд╕реБрд░рдХреНрд╖рд╛ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИред
"рд▓реЗрдХрд┐рди рдХреНрдпрд╛ рд╣реЛрдЧрд╛ рдЕрдЧрд░ рдореБрдЭреЗ рдФрд░ рдЕрдзрд┐рдХ рд╕реБрд░рдХреНрд╖рд┐рдд рд╕рдорд╛рдзрд╛рди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ?", рдЖрдк рдкреВрдЫрддреЗ рд╣реИрдВред рдареАрдХ рд╣реИ, рдореЗрд░реЗ рдкреАрдЫреЗ рдЖрдУред
рд╕рд╣реА рддрд░реАрдХрд╛
рдЖрдЗрдП рд╣рдорд╛рд░реЗ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреЗ рд▓рд┐рдП рдХрдИ рд╕реБрдзрд╛рд░ рдмрд┐рдВрджреБрдУрдВ рдкрд░ рдЪрд░реНрдЪрд╛ рдХрд░реЗрдВред
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, "рд╕рд╛рдзрд╛рд░рдг рд╣реИрд╢" (рдФрд░ рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ "рдирдордХреАрди рд╕рд╛рдзрд╛рд░рдг рд╣реИрд╢") рдХрд╛ рдореБрдЦреНрдп рджреЛрд╖ рдПрдХ рдХреНрд░реВрд░ рдмрд▓ рд╣рдорд▓реЗ (рд▓рдЧрднрдЧ рдкреНрд░рддрд┐ рдорд┐рдирдЯ рдЕрд░рдмреЛрдВ рд╣реИрд╢ ) рдХреА рдЕрдкреЗрдХреНрд╖рд╛рдХреГрдд рдЙрдЪреНрдЪ рдЧрддрд┐ рд╣реИред рдЗрд╕ рджреЛрд╖ рдХреЛ рд╕рдорд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ PBKDF2 рдЬреИрд╕реЗ рдПрдХ рд╡рд┐рд╢реЗрд╖ рдХреЗрдбреАрдПрдл-рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдорд┐рд▓рд╛ рд╣реИ рдЬреЛ рдореВрд▓ рд░реВрдк рд╕реЗ рдПрдВрдбреНрд░реЙрдЗрдб рдлреНрд░реЗрдорд╡рд░реНрдХ рджреНрд╡рд╛рд░рд╛ рд╕рдорд░реНрдерд┐рдд рд╣реИред рдмреЗрд╢рдХ, рдХреЗрдбреАрдПрдл рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рдмреАрдЪ рдХреБрдЫ рдЕрдВрддрд░ рд╣реИ рдФрд░ рдЖрдк рд╢рд╛рдпрдж рджреВрд╕рд░реЗ рдХреЛ рдЪреБрдирдирд╛ рдЪрд╛рд╣реЗрдВрдЧреЗ, рд▓реЗрдХрд┐рди рдпрд╣ рдЗрд╕ рд▓реЗрдЦ рдХреЗ рджрд╛рдпрд░реЗ рд╕реЗ рдмрд╛рд╣рд░ рд╣реИред рдореИрдВ рдЖрдкрдХреЛ рд▓реЗрдЦ рдХреЗ рдЕрдВрдд рдореЗрдВ рдЗрд╕ рд╡рд┐рд╖рдп рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдХрдИ рдЙрдкрдпреЛрдЧреА рд▓рд┐рдВрдХ рджреВрдВрдЧрд╛ред
рджреВрд╕рд░реЗ, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдЗрд╕ рдмрд┐рдВрджреБ рдкрд░ рдХреЛрдИ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдирд╣реАрдВ рд╣реИред рдЗрд╕реЗ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рдХрдИ рддрд░реАрдХреЗ рд╣реИрдВ рдФрд░ рдореИрдВ рд╕рдмрд╕реЗ рд╕рд░рд▓ рдФрд░ рд╕рдмрд╕реЗ рд╡рд┐рд╢реНрд╡рд╕рдиреАрдп рдПрдХ рджрд┐рдЦрд╛рдКрдВрдЧрд╛ред рдпрд╣ рджреЛ рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдФрд░ рдЙрдирдХреЗ рдЖрд╕рдкрд╛рд╕ рдХреБрдЫ рдХреЛрдб рдХрд╛ рдПрдХ рд╕реЗрдЯ рд╣реЛрдЧрд╛ред
рдЪрд▓реЛ рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ PBKDF2 рдХреБрдВрдЬреА рдмрдирд╛рдиреЗ рдХрд╛ рдХрд╛рд░рдЦрд╛рдирд╛ рд▓рд┐рдЦрддреЗ рд╣реИрдВред
object Pbkdf2Factory { private const val DEFAULT_ITERATIONS = 10000 private const val DEFAULT_KEY_LENGTH = 256 private val secretKeyFactory by lazy { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { SecretKeyFactory.getInstance("PBKDF2withHmacSHA1") } else { SecretKeyFactory.getInstance("PBKDF2withHmacSHA256") } } fun createKey( passphraseOrPin: CharArray, salt: ByteArray, iterations: Int = DEFAULT_ITERATIONS, outputKeyLength: Int = DEFAULT_KEY_LENGTH ): SecretKey { val keySpec = PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength) return secretKeyFactory.generateSecret(keySpec) } }
рдЕрдм рдЗрд╕ рдлреИрдХреНрдЯреНрд░реА рд╕реЗ рд▓реИрд╕ рд╣реЛрдХрд░ рд╣рдордиреЗ рдЕрдкрдиреЗ savePin()
рдФрд░ pinIsValid()
рддрд░реАрдХреЛрдВ рдХреЛ pinIsValid()
fun savePin(pin: String) { val salt = Salt.generate() val secretKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) val encodedKey = Base64.encodeToString(secretKey.encoded, Base64.DEFAULT) val encodedSalt = Base64.encodeToString(salt, Base64.DEFAULT) preferences.edit() .putString(StorageKey.KEY, encodedKey) .putString(StorageKey.SALT, encodedSalt) .apply() pinIsCreated.value = true }
private fun pinIsValid(pin: String): Boolean { val encodedSalt = preferences.getString(StorageKey.SALT, null) val encodedKey = preferences.getString(StorageKey.KEY, null) val salt = Base64.decode(encodedSalt, Base64.DEFAULT) val storedKey = Base64.decode(encodedKey, Base64.DEFAULT) val enteredKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) return storedKey contentEquals enteredKey.encoded }
рдЗрд╕ рдкреНрд░рдХрд╛рд░, рд╣рдордиреЗ рдЕрдкрдиреЗ рдкрд┐рдЫрд▓реЗ рд╕рдорд╛рдзрд╛рди рдХреЗ рдореБрдЦреНрдп рджреЛрд╖ рдХреЛ рдХрдо рдХрд░ рджрд┐рдпрд╛ рд╣реИред рдпрд╣ рдЕрдЪреНрдЫрд╛ рд╣реИ, рдФрд░ рдЕрдм рд╣рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдЬреЛрдбрд╝рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдЗрд╕реЗ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЗрди рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреЛ рд▓реЗрдВрдЧреЗ:
- рдЯрд┐рдВрдХ - рдПрдХ рдмрд╣реБ-рднрд╛рд╖рд╛, рдХреНрд░реЙрд╕-рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдо рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдЬреЛ рдХреНрд░рд┐рдкреНрдЯреЛрдЧреНрд░рд╛рдлрд╝рд┐рдХ рдПрдкреАрдЖрдИ рдкреНрд░рджрд╛рди рдХрд░рддреА рд╣реИ рдЬреЛ рджреБрд░реБрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реБрд░рдХреНрд╖рд┐рдд, рдЖрд╕рд╛рди рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдФрд░ рд╣рд╛рд░реНрдб (рдПрд░) рд╣реИред
- Jetpack Security - рд╕реБрд░рдХреНрд╖рд╛ рд╕рд░реНрд╡реЛрддреНрддрдо рдкреНрд░рдерд╛рдУрдВ рдХрд╛ рдкрд╛рд▓рди рдХрд░рдХреЗ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯреЗрдб рдлрд╝рд╛рдЗрд▓реЛрдВ рдФрд░ рд╕рд╛рдЭрд╛ рдкреНрд░рд╛рдердорд┐рдХрддрд╛рдУрдВ рдХреЛ рдкрдврд╝реЗрдВ рдФрд░ рд▓рд┐рдЦреЗрдВред
рдПрдХ рдЕрдЪреНрдЫрд╛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯреЗрдб рд╕реНрдЯреЛрд░реЗрдЬ рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдРрд╕рд╛ рдХреЛрдб рд▓рд┐рдЦрдирд╛ рд╣реЛрдЧрд╛:
class App : Application() { ... val encryptedStorage by lazy { EncryptedSharedPreferences.create( "main_storage", "main_storage_key", this, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) } ... }
рдмрд╕ рдЗрддрдирд╛ рд╣реАред рдмрд╛рдж рдореЗрдВ, рд╣рдо рдЗрд╕рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдЬреИрд╕реЗ рдХрд┐ рдпрд╣ SharedPreferences
рдереЗ, рд▓реЗрдХрд┐рди рд╕рднреА рдбреЗрдЯрд╛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд┐рдП рдЬрд╛рдПрдВрдЧреЗред рдЕрдм рд╣рдо рдкрд┐рдЫрд▓реЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рдЖрд╕рд╛рдиреА рд╕реЗ рдмрджрд▓ рд╕рдХрддреЗ рд╣реИрдВред
class CreatePinViewModel(application: Application) : AndroidViewModel(application) { ... private val preferences by lazy { getApplication<App>().encryptedStorage } ... }
class InputPinViewModel(application: Application) : AndroidViewModel(application) { ... private val preferences by lazy { getApplication<App>().encryptedStorage } ... }
рдЖрдЗрдП рд╕рдВрдХреНрд╖реЗрдк рдХреЛ рд╕рдВрдХреНрд╖реЗрдк рдореЗрдВ рдкреНрд░рд╕реНрддреБрдд рдХрд░реЗрдВред рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рдкрд┐рди рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдПрдХ рд╕реБрд░рдХреНрд╖рд┐рдд рдХреБрдВрдЬреА рд╣реИ, рдФрд░ рдЗрд╕реЗ рд╕реНрдЯреЛрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдХрд╛рдлреА рд╡рд┐рд╢реНрд╡рд╕рдиреАрдп рджреГрд╖реНрдЯрд┐рдХреЛрдг рд╣реИред рдпрд╣ рдЕрдЪреНрдЫрд╛ рд▓рдЧ рд░рд╣рд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдкрд░реНрдпрд╛рдкреНрдд рдирд╣реАрдВ рд╣реИред рдХреНрдпрд╛ рд╣реЛрдЧрд╛ рдЕрдЧрд░ рд╣рдо рдпрд╣ рдорд╛рди рд▓реЗрдВ рдХрд┐ рд╣рдорд▓рд╛рд╡рд░ рдиреЗ рд╣рдорд╛рд░реЗ рдбрд┐рд╡рд╛рдЗрд╕ рддрдХ рдкрд╣реБрдВрдЪ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд▓реА рд╣реИ рдФрд░ рдЙрд╕рд╕реЗ рдкреВрд░рд╛ рдбреЗрдЯрд╛ рдирд┐рдХрд╛рд▓ рд▓рд┐рдпрд╛ рд╣реИред рд╕рд┐рджреНрдзрд╛рдВрдд рд░реВрдк рдореЗрдВ, рдЗрд╕ рд╕рдордп рдбреЗрдЯрд╛ рдХреЛ рдбрд┐рдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрд╕рдХреЗ рдкрд╛рд╕ рд╕рднреА рдШрдЯрдХ рд╣реИрдВред рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХреЛ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рджреЛ рдЪреАрдЬреЗрдВ рд╣рд╛рд╕рд┐рд▓ рдХрд░рдиреА рд╣реИрдВ:
- рдПрдХ рдкрд┐рди рдмрд┐рд▓реНрдХреБрд▓ рднреА рд╕рдВрдЧреНрд░рд╣реАрдд рдирд╣реАрдВ рд╣реИ
- рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдСрдкрд░реЗрд╢рди рдкрд┐рди рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╣реИрдВ
рдкреВрд░реЗ рдХреЛрдб рдХреЛ рджреЛрдмрд╛рд░рд╛ рд▓рд┐рдЦреЗ рдмрд┐рдирд╛ рд╣рдо рдЗрди рд▓рдХреНрд╖реНрдпреЛрдВ рдХреЛ рдХреИрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ? рдпрд╣ рдЖрд╕рд╛рди рд╣реИ! Insofar рдЬреИрд╕рд╛ рдХрд┐ рд╣рдо Tink рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рд╣рдо рдЗрд╕рдХреЗ рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдлреАрдЪрд░ рдХреЛ рд╕рдВрдмрдВрдзрд┐рдд рдбреЗрдЯрд╛ рдХреЗ рд░реВрдк рдореЗрдВ рд▓рд╛рдЧреВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
рд╕рдВрдмрджреНрдз рдбреЗрдЯрд╛ рдХреЛ рдкреНрд░рдорд╛рдгрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╕рдВрдмрджреНрдз рдбреЗрдЯрд╛ рд╡реИрдХрд▓реНрдкрд┐рдХ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдпрд╣ рдкреИрд░рд╛рдореАрдЯрд░ рд╢реВрдиреНрдп рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ рд╢реВрдиреНрдп рдорд╛рди рдПрдХ рдЦрд╛рд▓реА (рд╢реВрдиреНрдп-рд▓рдВрдмрд╛рдИ) рдмрд╛рдЗрдЯ рд╕рд░рдгреА рдХреЗ рдмрд░рд╛рдмрд░ рд╣реИред рд╕рдлрд▓ рдбрд┐рдХреНрд░рд┐рдкреНрд╢рди рдХреЗ рд▓рд┐рдП рд╕рдорд╛рди рд╕рдВрдмрджреНрдзрддрд╛ рд╕рд┐рдлрд░рдЯреЗрдХреНрд╕реНрдЯ рдХреЗ рд╕рд╛рде рдкреНрд░рджрд╛рди рдХреА рдЬрд╛рдиреА рдЪрд╛рд╣рд┐рдПред
рдпрд╣ рдмрд╛рдд рд╣реИ! рд╣рдо рдЕрдкрдиреЗ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рд▓рдХреНрд╖реНрдпреЛрдВ рдХреЛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдВрдмрдВрдзрд┐рдд рдбреЗрдЯрд╛ рдХреЗ рд░реВрдк рдореЗрдВ рдПрдХ рдкрд┐рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЗрд╕ рдкреНрд░рдХрд╛рд░, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ рдХреЛ рдбрд┐рдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХреА рд╕рдВрднрд╛рд╡рдирд╛ рдпрд╛ рдЕрд╕рдВрднрд╡рддрд╛ рдкрд┐рди рд╢реБрджреНрдзрддрд╛ рдХреЗ рд╕рдВрдХреЗрддрдХ рдХреЗ рд░реВрдк рдореЗрдВ рдХрд╛рд░реНрдп рдХрд░реЗрдЧрд╛ред рдпрд╣ рдпреЛрдЬрдирд╛ рдЖрдорддреМрд░ рдкрд░ рдирд┐рдореНрдирд╛рдиреБрд╕рд╛рд░ рдХрд╛рдо рдХрд░рддреА рд╣реИ:

рдпрджрд┐ рдХреЛрдИ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЧрд▓рдд рдкрд┐рди рджрд░реНрдЬ рдХрд░рддрд╛ рд╣реИ, рддреЛ рдЖрдкрдХреЛ рдПрдХреНрд╕реЗрд╕ рдЯреЛрдХрди рдХреЛ рдбрд┐рдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддреЗ рд╕рдордп GeneralSecurityException рдкреНрд░рд╛рдкреНрдд рд╣реЛрдЧреАред рддреЛ, рдЕрдВрддрд┐рдо рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрд╛рдИ рджреЗ рд╕рдХрддрд╛ рд╣реИ:
рдХреЛрдб рджрд┐рдЦрд╛рдУ class CreatePinViewModel(application: Application): AndroidViewModel(application) { ... private val fakeAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXQiOiJXZSdyZSBoaXJpbmcgOykifQ.WZrEWG-l3VsJzJrbnjn2BIYO68gHIGyat6jrw7Iu-Rw" private val preferences by lazy { getApplication<App>().encryptedStorage } private val aead by lazy { getApplication<App>().pinSecuredAead } ... fun savePin(pin: String) { val salt = Salt.generate() val secretKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) val encryptedToken = aead.encrypt( fakeAccessToken.toByteArray(), secretKey.encoded ) preferences.edit { putString(StorageKey.TOKEN, Base64.encodeToString( encryptedToken, Base64.DEFAULT )) putString(StorageKey.SALT, Base64.encodeToString(salt, Base64.DEFAULT)) putBoolean(StorageKey.PIN_IS_ENABLED, true) } ... } }
class InputPinViewModel(application: Application) : AndroidViewModel(application) { ... private val preferences by lazy { getApplication<App>().encryptedStorage } private val aead by lazy { getApplication<App>().pinSecuredAead } fun authenticate(pin: String) { authenticationState.value = if (pinIsValid(pin)) { AuthenticationState.AUTHENTICATED } else { AuthenticationState.INVALID_AUTHENTICATION } } private fun pinIsValid(pin: String): Boolean { val salt = Base64.decode( preferences.getString(StorageKey.SALT, null), Base64.DEFAULT ) val secretKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) val token = try { val encryptedToken = Base64.decode( preferences.getString(StorageKey.TOKEN, null), Base64.DEFAULT ) aead.decrypt(encryptedToken, secretKey.encoded) } catch (e: GeneralSecurityException) { null } return token?.isNotEmpty() ?: false } }
рдЕрдЪреНрдЫрд╛ рдкрд░рд┐рдгрд╛рдо! рдЕрдм рд╣рдо рдкрд┐рди рдХреЛ рд╕реНрдЯреЛрд░ рдирд╣реАрдВ рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рдФрд░ рд╕рднреА рдбреЗрдЯрд╛ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рдпрджрд┐ рдЖрдк рдЪрд╛рд╣рддреЗ рд╣реИрдВ рддреЛ рдмреЗрд╢рдХ, рдЗрд╕ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рдмреЗрд╣рддрд░ рдмрдирд╛рдиреЗ рдХреЗ рдХрдИ рддрд░реАрдХреЗ рд╣реИрдВред рдореИрдВрдиреЗ рд╕рд┐рд░реНрдл рдореВрд▓ рд╕рд┐рджреНрдзрд╛рдВрдд рджрд┐рдЦрд╛рдпрд╛ рд╣реИред
рд▓реЗрдХрд┐рди рд░реБрдХреЛ, рдмрд╛рдпреЛрдореЗрдЯреНрд░рд┐рдХреНрд╕ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдХреНрдпрд╛?
рдореБрдЭреЗ рдирд╣реАрдВ рд▓рдЧрддрд╛ рдХрд┐ рд╕реБрд░рдХреНрд╖рд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ "рдмреЙрдпреЛрдореАрдЯреНрд░рд┐рдХреНрд╕" рд╣реИред рдореИрдВ рдЗрд╕рдХрд╛ рдирд╛рдо "рдПрдХ рдмрд╣реБрдд рд╣реА рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╕реБрд╡рд┐рдзрд╛" рд░рдЦреВрдВрдЧрд╛ред рдФрд░ рдпрд╣ рд╕реБрд╡рд┐рдзрд╛ рдФрд░ рд╕реБрд░рдХреНрд╖рд╛ рдХреЗ рдмреАрдЪ рдПрдХ рдмрд╣реБрдд рдкреБрд░рд╛рдирд╛ рдкрд╡рд┐рддреНрд░ рдпреБрджреНрдз рд╣реИред рд▓реЗрдХрд┐рди рдЕрдзрд┐рдХрд╛рдВрд╢ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЗрд╕ рддрд░рд╣ рдХреЗ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЛ рдкрд╕рдВрдж рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рд╣рдореЗрдВ рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЗ рд░реВрдк рдореЗрдВ рдЗрд╕реЗ рдпрдерд╛рд╕рдВрднрд╡ рд╕реБрд░рдХреНрд╖рд┐рдд рд░реВрдк рд╕реЗ рд▓рд╛рдЧреВ рдХрд░рдирд╛ рд╣реИред
рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдмрд╛рдпреЛрдореЗрдЯреНрд░рд┐рдХ рдХреЛрд░ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХрд╛рдлреА рдореБрд╢реНрдХрд┐рд▓ рд╣реИред рдЗрд╕рд▓рд┐рдП рдореИрдВ рдЖрдкрдХреЛ рдХреБрдЫ рд╕рд╛рдорд╛рдиреНрдп рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╕рд┐рджреНрдзрд╛рдВрдд рджрд┐рдЦрд╛рдиреЗ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рдХрд░реВрдВрдЧрд╛ рдФрд░ рдХреБрдЫ рд╕реНрдкрд╖реНрдЯреАрдХрд░рдг рджреВрдВрдЧрд╛ред рдЗрд╕рдХреЗ рдмрд╛рдж рд╣рдо рдХреЛрдб рдореЗрдВ рдЧрд╣рд░рд╛ рдЧреЛрддрд╛ рд▓рдЧрд╛рдПрдБрдЧреЗред

рдЗрд╕ рдпреЛрдЬрдирд╛ рдореЗрдВ рдПрдХ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдмрд╛рд░реАрдХрд┐рдпрд╛рдБ рд╢рд╛рдорд┐рд▓ рд╣реИрдВ: рдЧреБрдкреНрдд рдХреБрдВрдЬреА рдбрд┐рд╕реНрдХ рдкрд░ рд╕рд╣реЗрдЬреА рдЬрд╛рддреА рд╣реИ ред рдмреЗрд╢рдХ рдПрдХ рд╕рд╛рджреЗ рдкрд╛рда рдХреЗ рд░реВрдк рдореЗрдВ рдирд╣реАрдВ, рд▓реЗрдХрд┐рди рдлрд┐рд░ рднреАред
рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рд╣рдордиреЗ рдХреАрд╕реНрдЯреЛрд░ рдореЗрдВ рдПрдХ рдирдИ рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдХреБрдВрдЬреА рдмрдирд╛рдИ рд╣реИ рдФрд░ рд╣рдо рдЗрд╕ рдХреБрдВрдЬреА рдХрд╛ рдЙрдкрдпреЛрдЧ рд╣рдорд╛рд░реА рдЧреБрдкреНрдд рдХреБрдВрдЬреА рдХреЛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░рддреЗ рд╣реИрдВ рдЬреЛ рдХрд┐ рдкрд┐рди рд╕реЗ рд▓реА рдЧрдИ рд╣реИред рдЗрд╕ рддрд░рд╣ рдХреА рд╕реНрдХреАрдо рд╣рдореЗрдВ рдСрдереЗрдВрдЯрд┐рдХреЗрд╢рди рдореЗрдердб рдХреЛ рдмрджрд▓рддреЗ рд╕рдордп рд╕рднреА рдбреЗрдЯрд╛ рдХреЛ рджреЛрдмрд╛рд░рд╛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдирд╣реАрдВ рдХрд░рдиреЗ рджреЗрддреА рд╣реИред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдЕрднреА рднреА рдПрдХ рдкрд┐рди рджрд░реНрдЬ рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рд╣реИ рдпрджрд┐ рдмрд╛рдпреЛрдореЗрдЯреНрд░рд┐рдХ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХрд┐рд╕реА рднреА рдХрд╛рд░рдг рд╕реЗ рд╡рд┐рдлрд▓ рд╣реЛ рдЧрдпрд╛ рдерд╛ред рдареАрдХ рд╣реИ, рдмрд╣реБрдд рдХреЛрдб рд▓рд┐рдЦрддреЗ рд╣реИрдВред
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдореИрдВ рдкрд┐рди рдирд┐рд░реНрдорд╛рдг рдкреНрд░рд╡рд╛рд╣ рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рджрд┐рдЦрд╛рдКрдВрдЧрд╛:
рдХреЛрдб рджрд┐рдЦрд╛рдУ class CreatePinViewModel(application: Application): AndroidViewModel(application) { companion object { private const val ANDROID_KEY_STORE = "AndroidKeyStore" private const val KEY_NAME = "biometric_key" } ... val biometricEnableDialog = MutableLiveData<SingleLiveEvent<Unit>>() val biometricParams = MutableLiveData<BiometricParams>() val authenticationCallback = object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) } override fun onAuthenticationSucceeded(result: AuthenticationResult) { super.onAuthenticationSucceeded(result) val encryptedSecretKey = result.cryptoObject?.cipher?.doFinal( secretKey.encoded ) preferences.edit { putString(StorageKey.KEY, Base64.encodeToString( encryptedSecretKey, Base64.DEFAULT )) } pinIsCreated.postValue(true) } override fun onAuthenticationFailed() { super.onAuthenticationFailed() } } ... private val biometricManager by lazy { getApplication<App>().biometricManager } private val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE) private lateinit var secretKey: SecretKey ... fun enableBiometric(isEnabled: Boolean) { generateKey() val cipher = createCipher().also { preferences.edit { putString(StorageKey.KEY_IV, Base64.encodeToString(it.iv, Base64.DEFAULT)) } } val promptInfo = createPromptInfo() val cryptoObject = BiometricPrompt.CryptoObject(cipher) if (isEnabled) { biometricParams.value = BiometricParams(isEnabled, promptInfo, cryptoObject) } else { pinIsCreated.value = true } } private fun createPromptInfo(): BiometricPrompt.PromptInfo { return BiometricPrompt.PromptInfo.Builder() .setTitle("Create biometric authorization") .setSubtitle("Touch your biometric sensor") .setNegativeButtonText("Cancel") .build() } private fun generateKey() { try { keyStore.load(null) val keyProperties = PURPOSE_ENCRYPT or PURPOSE_DECRYPT val builder = KeyGenParameterSpec.Builder(KEY_NAME, keyProperties) .setBlockModes(BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) val keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM_AES, ANDROID_KEY_STORE ) keyGenerator.run { init(builder.build()) generateKey() } } catch (e: Exception) { authenticationCallback.onAuthenticationError( BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL, e.localizedMessage ) } } private fun createCipher(): Cipher { val key = with(keyStore) { load(null) getKey(KEY_NAME, null) } return Cipher.getInstance( "$KEY_ALGORITHM_AES/$BLOCK_MODE_CBC/$ENCRYPTION_PADDING_NONE" ).apply { init(Cipher.ENCRYPT_MODE, key) } } }
рдореБрдЭреЗ рдЦреБрд╢реА рд╣реЛрдЧреА рдЕрдЧрд░ Google рдиреЗ Tink in Biometrics рдХреЛ рд╢рд╛рдорд┐рд▓ рдХрд┐рдпрд╛, рд▓реЗрдХрд┐рди ... рд╣рдореЗрдВ рдЗрд╕ рдмрд╛рдпрд▓рд░рдкреНрд▓реЗрдЯ рдХреЛрдб рдХреЛ рд╕рд┐рдлрд░ рдФрд░ рдХреАрд╕реНрдЯреЛрд░ рдХреЗ рд╕рд╛рде рд▓рд┐рдЦрдирд╛ рд╣реЛрдЧрд╛ред рдпрд╣ рдХреЛрдб рдЙрди рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдХрд╛рдлреА рдкрд░рд┐рдЪрд┐рдд рд╣реИ, рдЬреЛ рдПрдВрдбреНрд░реЙрдЗрдб рдореЗрдВ рдХреНрд░рд┐рдкреНрдЯреЛрдЧреНрд░рд╛рдлреА рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдореИрдВ рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдкреИрдбрд┐рдВрдЧ рдкрд░ рдЖрдкрдХрд╛ рдзреНрдпрд╛рди рджреЗрдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВред рд╣рд╛рдВ, рдкреЗрдбрд┐рдВрдЧ рдУрд░реЗрдХрд▓ рд╣рдорд▓реЗ рдХреЛ рд░реЛрдХрдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдо рдкреИрдбрд┐рдВрдЧ рдХрд╛ рдЙрдкрдпреЛрдЧ рдмрд┐рд▓реНрдХреБрд▓ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВред рдЗрд╕ рдкреНрд░рдХрд╛рд░, рдбрд┐рд╕реНрдХ рдкрд░ рдЧреБрдкреНрдд рдХреБрдВрдЬреА рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддреЗ рд╕рдордп рд╣рдо рдЬреЛрдЦрд┐рдореЛрдВ рдХреЛ рдХрдо рдХрд░рддреЗ рд╣реИрдВред
рдмреЙрдпреЛрдореАрдЯреНрд░рд┐рдХ рдЬрд╛рдБрдЪ рдХреЗ рд▓рд┐рдП рдХреЛрдб рдмрд╣реБрдд рд╕рдорд╛рди рд╣реИ:
рдХреЛрдб рджрд┐рдЦрд╛рдУ class InputPinViewModel(application: Application) : AndroidViewModel(application) { companion object { private const val ANDROID_KEY_STORE = "AndroidKeyStore" private const val KEY_NAME = "biometric_key" } ... val biometricErrorMessage = MutableLiveData<SingleLiveEvent<String>>() val biometricParams = MutableLiveData<BiometricParams>() ... private val biometricManager by lazy { getApplication<App>().biometricManager } private val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE) val authenticationCallback = object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) } override fun onAuthenticationSucceeded(result: AuthenticationResult) { super.onAuthenticationSucceeded(result) val encryptedSecretKey = Base64.decode( preferences.getString(StorageKey.KEY, ""), Base64.DEFAULT ) val secretKey = result.cryptoObject?.cipher?.doFinal(encryptedSecretKey) val token = try { val encryptedToken = Base64.decode( preferences.getString(StorageKey.TOKEN, null), Base64.DEFAULT ) aead.decrypt(encryptedToken, secretKey) } catch (e: GeneralSecurityException) { null } val state = if (token?.isNotEmpty() == true) { AuthenticationState.AUTHENTICATED } else { AuthenticationState.INVALID_AUTHENTICATION } authenticationState.postValue(state) } override fun onAuthenticationFailed() { super.onAuthenticationFailed() } } ... fun biometricAuthenticate() { if (preferences.contains(StorageKey.KEY)) { when (biometricManager.canAuthenticate()) { BiometricManager.BIOMETRIC_SUCCESS -> { val promptInfo = createPromptInfo() val cryptoObject = BiometricPrompt.CryptoObject(createCipher()) biometricParams.value = BiometricParams(promptInfo, cryptoObject) } } } else { biometricErrorMessage.value = SingleLiveEvent( "Biometric authentication isn't configured" ) } } ... private fun createPromptInfo(): BiometricPrompt.PromptInfo { return BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Cancel") .build() } private fun createCipher(): Cipher { val key = with(keyStore) { load(null) getKey(KEY_NAME, null) } return Cipher.getInstance( "$KEY_ALGORITHM_AES/$BLOCK_MODE_CBC/$ENCRYPTION_PADDING_NONE" ).apply { val iv = Base64.decode( preferences.getString(StorageKey.KEY_IV, null), Base64.DEFAULT ) init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv)) } } }
authenticationCallback.onAuthenticationSucceeded
рдзреНрдпрд╛рди authenticationCallback.onAuthenticationSucceeded
, рдЗрд╕рдореЗрдВ рдкреЛрд╕реНрдЯ-рдмрд╛рдпреЛрдореЗрдЯреНрд░рд┐рдХ рдСрдереЗрдВрдЯрд┐рдХреЗрд╢рди рдХрд╛ рдореБрдЦреНрдп рддрд░реНрдХ рд╣реЛрддрд╛ рд╣реИред рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдпрд╣ pinIsValid()
рд╡рд┐рдзрд┐ рдХрд╛ рдПрдХ рд╡реИрдХрд▓реНрдкрд┐рдХ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реИред рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рджреЛ рдкрд┐рдЫрд▓реЗ рдХреЛрдб рдмреНрд▓реЙрдХ рдореЗрдВ рдХреНрдпрд╛ рд╣реЛ рд░рд╣рд╛ рд╣реИ, рдЗрд╕рдХреА рдХреЛрдИ рдордЬрдмреВрдд рд╕рдордЭ рдирд╣реАрдВ рд╣реИ, рддреЛ рдХреГрдкрдпрд╛ рдмрд╛рдпреЛрдореЗрдЯреНрд░рд┐рдХ рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рджрд╕реНрддрд╛рд╡реЗрдЬ рджреЗрдЦреЗрдВ ред
рдХреНрдпрд╛ рдореИрдВ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рд╕реБрд░рдХреНрд╖рд┐рдд рд╣реВрдВ?
рд╣рдордиреЗ рдПрдХ рдкрд┐рди рдФрд░ рдмрд╛рдпреЛрдореЗрдЯреНрд░рд┐рдХреНрд╕ рдХреЗ рд╕рд╛рде рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХрд╛ рдПрд╣рд╕рд╛рд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрд╣реБрдд рд╕рд╛рд░реА рд╢рд╛рдирджрд╛рд░ рдЪреАрдЬреЗрдВ рдХреА рд╣реИрдВ, рд▓реЗрдХрд┐рди рдХреНрдпрд╛ рдпрд╣ рдЗрддрдирд╛ рд╡рд┐рд╢реНрд╡рд╕рдиреАрдп рдФрд░ рд╕реБрд░рдХреНрд╖рд┐рдд рд╣реИ? рдмреЗрд╢рдХ, рд╣рдордиреЗ рдЕрдкрдирд╛ рд╕рд░реНрд╡рд╢реНрд░реЗрд╖реНрда рдкреНрд░рджрд░реНрд╢рди рдХрд┐рдпрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рдмрд┐рдВрджреБ рд╣реИрдВред
рдПрдХ рдХреНрд▓рд╛рд╕рд┐рдХ рдкрд┐рди рдореЗрдВ рдХреЗрд╡рд▓ рдЪрд╛рд░ рдЕрдВрдХ рд╣реЛрддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕рдореЗрдВ рдкреНрд░рд╡реЗрд╢ рднреА рдмрд╣реБрдд рдХрдо рд╣реЛрддрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП, рдЗрд╕ рддрд░рд╣ рдХрд╛ рдХреЛрдб рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд╛рдлреА рд╕реБрд░рдХреНрд╖рд┐рдд рдирд╣реАрдВ рд╣реИред рд╣рдордиреЗ рдЬреЛ рдХреБрдЫ рднреА рдХрд┐рдпрд╛ рд╣реИ рдЙрд╕рдХреЗ рдмрд╛рд╡рдЬреВрдж, рдПрдХ рдореМрдХрд╛ рд╣реИ рдХрд┐ рдПрдХ рдШреБрд╕рдкреИрдард┐рдпрд╛ рдЗрд╕ рдХреЛрдб рдХреЛ рдХреНрд░реИрдХ рдХрд░ рд╕рдХрддрд╛ рд╣реИред рд╣рд╛рдБ, рдЙрд╕реЗ рдЖрдкрдХреЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреА рд░рд┐рд╡рд░реНрд╕ рдЗрдВрдЬреАрдирд┐рдпрд░рд┐рдВрдЧ рдХреЛ рдкреВрд░рд╛ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ рдФрд░ рд╕рдордЭрдирд╛ рд╣реЛрдЧрд╛ рдХрд┐ рдЖрдк рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдбреЗрдЯрд╛ рдХреЛ рдХреИрд╕реЗ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдлрд┐рд░ рднреАред рдпрджрд┐ рдХреЛрдИ рд╣рдорд▓рд╛рд╡рд░ рдкрд░реНрдпрд╛рдкреНрдд рд░реВрдк рд╕реЗ рдкреНрд░реЗрд░рд┐рдд рд╣реЛрддрд╛ рд╣реИ рддреЛ рд╡рд╣ рдмрд┐рдирд╛ рдХрд┐рд╕реА рд╣рд┐рдЪрдХрд┐рдЪрд╛рд╣рдЯ рдХреЗ рдРрд╕рд╛ рдХрд░реЗрдЧрд╛ред
рджреВрд╕рд░рд╛ рдмрд┐рдВрджреБ рд░реВрдЯ рдХрд┐рдП рдЧрдП рд╕реНрдорд╛рд░реНрдЯрдлрд╝реЛрди рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╣реИред рдЬрдм рдпрд╣ рдирд┐рд╣рд┐рдд рдЙрдкрдХрд░рдгреЛрдВ рдХреА рдмрд╛рдд рдЖрддреА рд╣реИ, рддреЛ рдЖрдк рдЕрдкрдиреЗ рд╕рднреА рд╕реБрд░рдХреНрд╖рд╛ рдЖрд╢реНрд╡рд╛рд╕рди рдкреНрд░рдпрд╛рд╕реЛрдВ рдХреЛ рджреВрд░ рдлреЗрдВрдХ рд╕рдХрддреЗ рд╣реИрдВред рд░реВрдЯ рдПрдХреНрд╕реЗрд╕ рдХреЗ рд╕рд╛рде рдХреЛрдИ рднреА рдореИрд▓рд╡реЗрдпрд░ рд╕рднреА рд╕реБрд░рдХреНрд╖рд╛ рддрдВрддреНрд░ рдХреЛ рдмрд╛рдпрдкрд╛рд╕ рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реИред рдЗрд╕рд▓рд┐рдП, рдЖрдкрдХреЛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ рдЕрддрд┐рд░рд┐рдХреНрдд рд╕реБрд░рдХреНрд╖рд╛ рд╕реБрд╡рд┐рдзрд╛рдПрдВ рдФрд░ рдЪреЗрдХ рдЬреЛрдбрд╝рдирд╛ рд╣реЛрдЧрд╛ред рдореИрдВ рдЖрдкрдХреЛ рдЗрди рджреЛрд╖реЛрдВ рдХреЛ рдХрдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рджреЛ рд╕рдмрд╕реЗ рд╕рд░рд▓ рдЪреАрдЬреЗрдВ рд╕реБрдЭрд╛рддрд╛ рд╣реВрдВ:
- рд╕реЗрдлреНрдЯреАрдиреЗрдЯ - рдпрд╣ рд╕реЗрд╡рд╛рдУрдВ рдФрд░ рдПрдкреАрдЖрдИ рдХрд╛ рдПрдХ рд╕реЗрдЯ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ рдЬреЛ рдЖрдкрдХреЗ рдРрдк рдХреЛ рд╕реБрд░рдХреНрд╖рд╛ рдЦрддрд░реЛрдВ рд╕реЗ рдмрдЪрд╛рдиреЗ рдореЗрдВ рдорджрдж рдХрд░рддрд╛ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдЫреЗрдбрд╝рдЫрд╛рдбрд╝, рдЦрд░рд╛рдм рдпреВрдЖрд░рдПрд▓, рд╕рдВрднрд╛рд╡рд┐рдд рд╣рд╛рдирд┐рдХрд╛рд░рдХ рдРрдк рдФрд░ рдирдХрд▓реА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╢рд╛рдорд┐рд▓ рд╣реИрдВред
- Obfuscation - рдХреГрдкрдпрд╛ рдпрд╛рдж рд░рдЦреЗрдВ рдХрд┐ ProGuard рдПрдХ рдЖрдХреНрд╖реЗрдк рдЙрдкрдХрд░рдг рдирд╣реАрдВ рд╣реИ! ProGuard рдЫреЛрдЯрд╛ рдФрд░ рд╕рдВрд╕рд╛рдзрди рд╕рд┐рдХреБрдбрд╝рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╣реИ, рди рдХрд┐ рдЖрдкрддреНрддрд┐ рдпрд╛ рд╕реБрд░рдХреНрд╖рд╛ рдХреЗ рд▓рд┐рдПред рдбреЗрдХреНрд╕рдЧрд╛рд░реНрдб, рдбреЗрдХреНрд╕рдкреНрд░реЛрдЯреЗрдХреНрдЯрд░, рдЖрджрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред
рдЗрд╕ рд▓реЗрдЦ рдХреЗ рджреГрд╖реНрдЯрд┐рдХреЛрдгреЛрдВ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж рд╕реЗрдлреНрдЯреАрдиреЗрдЯ рдФрд░ рдСрдмрдлрд┐рдХреЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдПрдХ рдЕрдЪреНрдЫрд╛ рдЕрдЧрд▓рд╛ рдХрджрдо рд╣реИред рдпрджрд┐ рдЖрдк рдЧрд▓рддрд┐рдпрд╛рдБ, рд╕реБрд░рдХреНрд╖рд╛ рджреЛрд╖ рдпрд╛ рдЕрдиреНрдп рдмрдХрд╡рд╛рд╕ рдиреЛрдЯ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдХреГрдкрдпрд╛ рдореБрдЭреЗ рдмрддрд╛рдПрдБред рдЖрдк GitHub рдкрд░ рд▓реЗрдЦ рдХреЗ рд╕рднреА рдХреЛрдб рдкрд╛ рд╕рдХрддреЗ рд╣реИрдВред
рдФрд░ рдЕрдЧрд▓реА рдмрд╛рд░ рдореИрдВ рдЖрдкрдХреЛ рджрд┐рдЦрд╛рдКрдВрдЧрд╛ рдХрд┐ рдмреИрдХ-рдПрдВрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкрд┐рди рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреИрд╕реЗ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЬрд╛рдПред рджреЗрдЦрддреЗ рд░рд╣реЛред
рдЙрдкрдпреЛрдЧреА рд▓рд┐рдВрдХ