연관된 git https://github.com/choi-hyeseong/NFC_Sever (서버 구현) | https://github.com/choi-hyeseong/NfcReader (NFC 리더기 구현)
여러 안드로이드 애플리케이션 제작을 하던 중, 서버와 애플리케이션간의 의사소통의 필요성 및 안드로이드에서의 응답 처리의 필요성을 느껴 프로젝트를 통해 구현해보게 되었습니다. 여러 기업에서 사내 보안을 관리하기 위해 MDM (Mobile Device Management) 시스템을 구축 하는데, 이때 필요한 사항이 무엇인가를 생각해보고, 이를 최대한 구현해보는 과정이 되었습니다.
최초 회원가입 과정에서는 서버의 URL을 입력하고, 연결이 이루어진경우 안드로이드에서 랜덤한 UUID를 생성하게 되고, 이를 연결된 서버에서 GET요청을 통해 얻은 공개키를 이용하여 RSA 암호화를 진행해 서버로 전송합니다. 서버에서 정상적인 복호화가 이루어졌을경우, 10자리의 랜덤한 문자열 2개를 생성합니다. (인증키, 삭제키) 인증키는 웹소켓 통신에서 사용되며, 삭제키는 구현되지는 못했지만 MDM이 활성화 되어있을경우 애플리케이션을 삭제하지 못하는 상황에 대비하여 해당 코드를 입력할경우 애플리케이션이 삭제될 수 있도록 입력되는 코드입니다. 이렇게 생성된 2개의 키를 UUID를 이용하여 AES암호화를 통해 다시 클라이언트에게 응답으로써 보냅니다.
클라이언트(안드로이드)는 일정 주기로 서버에 연결을 시도하며, 연결이 이루어진경우 웹소켓 엔드포인트를 통해 웹소켓 접속을 시도합니다. 이때, 일정한 타임아웃 주기내에 서버로 UUID를 보내 정상적으로 등록된 기기라는것을 입증해야 합니다. 이때도 당연히 RSA암호화가 사용됩니다. 그후, 서버에서 주기적으로 클라이언트에게 PING을 날리며, 클라이언트는 다음 핑 주기전까지 PONG을 보내지 못하면 타임아웃이 이뤄져 소켓이 닫히게 됩니다. 핑-퐁 주기내에 서버나 NFC리더기로부터 MDM 태깅 신호가 들어왔을경우, 다음 핑 주기에 MDM 실행 데이터를 담아 클라이언트에게 보냅니다. 클라이언트는 이 데이터를 받아 MDM 여부를 변경하게 되고, 그 결과를 응답에 받아 보냅니다. 이 응답이 서버로 전송되었을경우 서버는 클라이언트의 MDM 여부를 DB에 저장합니다.
태깅된 데이터를 그대로 저장된 URL로 보냅니다. 클라이언트에서 이미 암호화가 진행되었으므로, 그대로 전송하는 역할을 맡습니다.
기업의 보안을 책임지는 MDM의 본래 목적에 부합하기 위해선, 통신 과정에서 와이어샤크등 다른 프로그램이나, 여러가지 경로를 통해 데이터가 노출되는 부분 및 DNS 조작을 통해 사설 서버로 우회를 진행하여 MDM을 강제적으로 실행시키는 방법이 있을거라 생각했습니다. 우선, 통신 과정에서의 데이터 노출을 막기 위해선, 서버가 받아야 하는 데이터는 RSA 암호화, 클라이언트가 받아야 하는 데이터는 AES (UUID)암호화를 진행하였습니다. 이 부분은 https 암호화 과정에서 영감을 받아 진행하게 되었습니다. 또한, RSA 암호화를 진행하였다 해도, 항상 같은 키를 이용하여 암호화 한 값은 결과값도 같기 때문에, 암호화 이전 데이터가 뭔지 몰라도, 해커는 암호화된 데이터를 그대로 서버로 전송할경우 동일한 결과를 얻을 수 있었습니다. 따라서, 전송되는 데이터에 현재 시간값을 추가하여, 이 시간값이 타임아웃 주기 밖일경우, 타임아웃 오류를 반환하도록 설정하였습니다. DNS 스푸핑을 통해 해커 개인서버로 우회되는 문제는 완벽하게 막을 방법이 떠오르지 않았지만, SSL 인증서를 통해, 해당 서버가 인증되었는지 여부를 확인하여 클라이언트에서 사전 차단을 하게 된다면 될것 같다는 생각을 하였습니다.
기존 SharedPreference는 루트 저장소에 xml의 형태로 키-값을 저장하였는데, 이를 평문으로 저장한다는 점때문에 보안에 취약할 것 같았습니다. 따라서 안드로이드 Security 에서 지원하는 EncryptedSharedPreference를 이용하여 안드로이드 키스토어에서 생성된 키를 가지고 암호화가 진행되어 데이터가 안전하게 저장될 수 있었습니다.
암-복호화를 진행할때 Cipher 객체를 통해 진행하게 되는데, 이때 암호화 방식/운용모드/패딩을 명시 할 수 있습니다. 처음에는 이 방식에 대해 잘 알지 못해 단순하게 .getInstance("RSA")로 호출 했으나, 서버에서의 복호화가 정상적으로 이루어지지 못했습니다. 그래서 해당 자료를 찾아보던중, 운용 모드와 패딩을 같게 명시해야 정상적으로 이루어지는것을 알게되어, 스프링 서버와 클라이언트의 패딩과 운용 모드를 일치시켜주니 정상적으로 진행되게 되었습니다.
APDU(Application Protocol Data Unit)을 통해 nfc간의 데이터 공유 (정확히는 HCE 태깅 과정)가 이루어집니다. 처음에는 low level의 데이터 교환에 익숙하지 않아 잘 작동되지 않았지만, 16진수 데이터를 byteArray에 넣어서 보내며, 이때 바이트당 들어갈 데이터는 고정 되어 있는것을 확인하여 자료를 찾아 적용해보니 잘 되었습니다. 다만, transceive의 리턴값으로 응답 데이터가 정상적으로 전송되지 않았는데, 이는 응답 데이터도 APDU 규격에 맞춰 전송해야 된다는 점을 알게되어, 기존 AID가 들어가던 위치에 응답데이터를 바이트화 하여 넣었더니 잘 동작하게 되었습니다.
NFC 리더기를 위 조합을 이용하여 Ndef방식을 통해 데이터를 읽어 서버로 전송하고자 했으나, 자료가 부족하였고 시도 과정에서 usb 소켓이 고장나는 참사가 발생하였습니다.. 다음 시도에는 더 많은 사전 조사와 자료조사를 병행하여 완벽하게 해냈으면 좋겠습니다/
안드로이드 최신버전에서는 기존 DPM(Device Policy Manager)의 기능을 점점 Deprecated처리하여 사용할 수 있는 기능의 제한이 있었습니다. 카메라 차단또한 해당 범주내에 포함되어 최신 버젼에서는 정상적인 차단이 불가능했습니다. 이를 해결하기 위해선 Android EMM(Enterprice Mobility Management) 시스템 및 삼성 Knox 라이브러리를 이용하는 방법을 거쳐 Device Owner 혹은 Profile Owner를 만들어야 했는데, 해당되는 자료가 부족했고, 개인 프로젝트에 적용하기엔 너무 범주가 커서 할 수 없이 적용할 수 없게 되었습니다. 만약 이 문제점을 해결한다면 삼성 녹스 라이브러리를 이용하여, 라이선스를 인증받고 디바이스 관리 기능을 사용하여 구현되지 못한 삭제 방지 기능 및 카메라 차단 기능을 활성화 하면 될 것 같다는 생각이 들었습니다.
서버-클라이언트 통신 과정에서는 잘 진행되어 지켜진 보안이, DB에서는 평문으로 저장되어 보안에 취약했습니다. 이를 보완할 방법을 강구하여 로컬에서도 보안을 잘 유지할 수 있도록 했으면 좋겠다는 생각이 들었습니다.
서비스와 프래그먼트(액티비티)간의 데이터 전송을 위해 브로드캐스팅을 이용하여 사소한 데이터를 전송했습니다.(MDM 활성화 / 서버 연결여부) 보안상으로 위험한 데이터는 전송하지 않았으나, 코드를 지저분하게 만드는 원인이 되기도 했습니다. Reactive 방식을 사용하여 좀더 효과적으로 코드를 리팩토링 해봐야 겠다는 다짐을 했습니다.