- [Android] SharedPreferences 와 KeyStore2022년 05월 11일 23시 58분 42초에 업로드 된 글입니다.작성자: 핀수728x90반응형
해당 글을 참고했습니다.
안전하게 민감정보 저장 이슈
해당 게시글은 Android 개발자에게 해당되는 이야기다. 민감정보에대해 개발을 하면서 서비스의 운용을 위해 필요한 정보를 받는 것은 자연스러운 일이다. 그중 대표적으로 비밀번호를 말할 수
secu-lee-ty.tistory.com
들어가며
다들 그렇겠지만 전역적으로 쓰이는 정보에 한해서는 SharedPreferences 를 이용해 정보를 저장해왔을 것이다.
나의 경우 로그인기능을 개발하면서 사용자 편의를 위해 로그인 정보 저장 기능을 만들고자 했었다.
그러려면 아이디와 비밀번호 모두를 SharedPreferences 에 저장해야하는데
비밀번호를 평문으로 저장하는 것은 너무 위험하고..
로그인 정보를 서버에 전송할 때 암호화 된 비밀번호를 전송하니
암호화된 비밀번호를 저장하면 어떨까? 했지만
그렇게 되면 로그인 정보 저장에 체크한 사용자 그렇지 않은 사용자 분기처리를 해서 서버에 전송해야한다.
그렇지 않으면 암호화 된 비밀번호를 한번더 암호화해서 전송하기 때문에
일치하는 사용자가 없을것이다.
그리고 저장된 비밀번호를 꺼내올때는 암호화된 정보를 불러와야하다 보니..
가령 비밀번호가 1234 라면
암호화된 1234가 불러와지는 것이다. (SHA-512 를 사용한다. -> 복호화가 불가능하다.)
**** 가 ********************* 가 되어 비밀번호란에 불러와지는 것임
그냥 비밀번호를 평문으로 저장하면 안될까?
어차피 실제 사용자들은 보이지도 않을텐데?
SharedPreferences
- XML 의 형태
- 접근 경로 : data/data/패키지명/shared_prefs
- 해당 경로는 관리자 권한을 가지고 있어야만 접근이 가능함
- 그러나 루팅을 통해 관리자 권한을 획득하면 정보가 그대로 공개될 위험이 있다.
너무 위험하다!
당시에 별다른 방도를 찾지 못한 나는 그냥 아이디 저장으로 만족해야했다.
그러다 안드로이드의 KeyStore 를 알게 되었다.
KeyStore
컨테이너라는 공간 속에 RSA 키페어 / AES 대칭키를 저장하는 시스템
앱 출시를 위해 서명할때 만난 적 있을 것이다.
이 친구를 이용해 해당 데이터를 암호화하고 암호화된 정보를 SharedPreferences 에 저장할 것이다.
RSA, AES 방식 중 나는 RSA 를 채택했다.
RSA
- 공개키 암호화 시스템 중 하나
- 공개키를 통해 암호화 (Encrypt)
- 비밀키를 통해 복호화 (Decrypt)
=> 누구나 어떤 메세지를 암호화할 수 있지만, 그것을 해독해 열람할 수 있는 것은 개인키를 가진 단 한사람
사용해보기
1) 키스토어 초기화
2) 암호화
3) 복호화
세가지 기능을 하는 메소드를 각각 만들고 이를 가지고 있는 클래스를 만들었다.
1) 키스토어 초기화
ALIAS : 키스토어에서 키를 생성하거나 불러올 때 사용되는 별칭public static final String ALIAS = "YOUR_ALIAS"; public static final String ANDROID_KEYSTORE = "AndroidKeyStore"; public static final String MODE = "RSA/ECB/PKCS1Padding"; public static void init() { try { KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); keyStore.load(null); if (!keyStore.containsAlias(ALIAS)) { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE); keyPairGenerator.initialize( new KeyGenParameterSpec.Builder( ALIAS, KeyProperties.PURPOSE_DECRYPT) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .build()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Cipher cipher = Cipher.getInstance(MODE); cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); } } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException | NoSuchPaddingException | KeyStoreException | CertificateException | IOException | InvalidKeyException e) { e.printStackTrace(); } }
2) 암호화public static String encryptData(String data) { String encryptedData = ""; try { KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); keyStore.load(null); PublicKey publicKey = keyStore.getCertificate(ALIAS).getPublicKey(); // 키스토어의 공개키 반환 Cipher cipher = Cipher.getInstance(MODE); // 운용모드/패딩 셋업 cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 암호화 준비 byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); // 매개변수로 받은 데이터 암호화 encryptedData = new String(Base64.encode(encrypted, Base64.DEFAULT), StandardCharsets.UTF_8); } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | NoSuchPaddingException e) { e.printStackTrace(); } return encryptedData; }
3) 복호화public static String decryptData(String encryptedData) { String decryptedData = ""; try { KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); keyStore.load(null); PrivateKey privateKey = (PrivateKey) keyStore.getKey(ALIAS, null); Cipher cipher = Cipher.getInstance(MODE); // 운용모드/패딩 셋업 cipher.init(Cipher.DECRYPT_MODE, privateKey); // 암호화된 인코딩 데이터 -> 디코딩 byte[] byteStr = Base64.decode(encryptedData.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); // 디코딩된 암호문 -> 복호화 후 문자열 변환 decryptedData = new String(cipher.doFinal(byteStr)); } catch (InvalidKeyException | UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } return decryptedData; }
이 과정에서 계속 암호화는 되는데 복호화를 하면 "" 으로 떨어지는 오류를 겪었다.
(IllegalBlockSizeException)
원인은 MODE 와 .setEncryptionPaddings() 의 매개변수,
그러니까 padding 방식이 일치하지 않아서 생긴 오류였다.
공식문서를 참고해서 자신이 원하는 암호화 방식에 맞는 것끼리 짝지어(?)주어야 한다.
자세하게 기억은 나지 않는데
MODE = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
RSA/ECB/PKCS1Padding
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
둘 중에 하나가..제대로 실행이 됐었다.......
찾아보고 수정을 해야겠다.
또한 RSA 는 암/복호화 하는데 시간이 다소 소요된다.
-> 성능 저하를 일으킬 수 있음
틀린 내용이 있으면 지적해주세요!
수정하도록 하겠습니다.
728x90반응형'Android > Android' 카테고리의 다른 글
[kotlin] 코틀린 기초 문법 다지기 - 2. class (0) 2022.06.12 [kotlin] 코틀린 기초 문법 다지기 - 1 (2) 2022.06.06 [Android] Google Play Developer API으로 앱 출시하기 (2) 2022.05.10 [Android] 앱 서명 (0) 2022.04.18 [Android] AndroidManifest.xml 의 package 속성 (0) 2022.04.18 다음글이 없습니다.이전글이 없습니다.댓글