핀수로그
  • [Android] SharedPreferences 와 KeyStore
    2022년 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
      반응형
      댓글