2021년 9월 ~ 12월까지 학점 연계형 인턴을 할 때였다. 딥러닝 개발도 하고 Android 개발도 하고 3개월동안 이것저것 해볼 때였다. 우리가 할 프로젝트는 보이스 피싱 탐지 앱 개발 프로젝트였고, 실제 출시 할 앱을 개발하는 것이었다. 그 당시에 아는 것도 없고 자신도 없을 때라 "우리가 만든 앱을 이렇게 막 출시해도 되나..?" 라는 생각 뿐이었다. 그래도 나름 열심히 했었고 딥러닝 파트를 일찍 끝내고 Android로 넘어와서 개발을 할 때 제목과 같은 프로세스를 설계했던 것을 기록에 남기고 싶어 블로그를 작성하게 되었다. "실시간 통화 중 보이스피싱 탐지 후 사용자에게 알림 프로세스" 를 설계한 과정을 얘기해보겠다.
말 그대로 전화가 오면 그 통화 상태가 보이스피싱인지 확인하여 사용자에게 알려주는 프로세스이다. 내가 개발했던 딥러닝 서버에서는 음성 파일을 입력으로 넣게 되면 해당 대화 내용이 일반적인 보이스피싱 내용인지 확인하여 확률을 출력하게된다. 그러면, 앱에서는 통화를 녹음하여 그 녹음 파일을 딥러닝 서버로 전송해야한다는 것이다. 하지만, 이를 실시간으로 해야하니 좀 더 깊이 고민할 수 밖에 없었다. 여러 테스트를 해본 결과 보이스피싱인지 확인할 때 필요한 음성 파일은 30초 ~ 1분 길이면 충분했다. 그래서, 통화 시작 후 자동으로 녹음을 시작해 1분 정도 녹음을 한 후 딥러닝 서버로 해당 파일을 전송해서 확률 값을 다시 앱으로 받아내는 것이었다.
하지만, Android 10 이후로는 보안상의 문제로 기본 앱이 아닌 서드파티 앱에서는 통화 중 상대방 목소리가 녹음되지 않는 이슈가 있었다. 이 앱은 통화 화면도 구현되어 기본 통화 앱으로 사용되는 앱이었다. 그래서, 녹음된 파일은 분석이 가능하나 실시간으로는 불가능 하다는 것이었다. 해당 이슈를 보고드리자 대표님께서는 실시간 보이스 피싱 탐지 기능을 빼라고 하셨다. 하지만, 실시간으로 보이스 피싱 탐지를 하지 않고 녹음된 파일만을 가지고 분석한다면 보이스 피싱 피해를 예방하기 힘들 것이라 생각했다. 그래서 해결 방안을 좀 더 찾아보겠다고 말씀드렸고 좀 더 고민을 해봤다.
사실 보안 문제로 막힌다면 뚫고 나갈 길을 찾기 어렵다는 것을 알고있다. 그래서, 다른 방향으로 접근해보았다. 서드파티 앱에서 자동으로 녹음하려고 한다면 문제가 발생했기에 기본 앱에서 녹음을 하게끔하고 출력은 서비스로 팝업을 띄우는 방식으로 해보면 어떨까라는 생각이 들었다. 물론 직접 녹음 버튼을 눌러야하는 번거로움이 있을 수 있다. 하지만, 실시간으로 보이스 피싱을 탐지할 수 있다면 이 방법도 괜찮다고 생각했다. 그래서, 먼저 유저 시나리오를 설계해보았다.
이와 같은 방향으로 흘러가게 되는데 이를 구현하기 위한 Flow 또한 설계해보았다.
이와 같은 Flow로 구현을 하게 된다. 핵심은 팝업을 띄울 Service 구현, 녹음 파일 완료 상태 확인, 소켓 통신 이 3가지이다.
BroadcastReceiver
를 활용해 전화가 왔을 때의 이벤트를 호출한다. 앱이 켜져있지 않은 상태에서 팝업 창을 띄우기 위해서는 Service
를 활용해 팝업 창을 띄울 수 있다.
녹음을 시작했을 때 외부 저장소 특정 경로에 녹음 파일이 생성된다. (녹음이 완료되고나서 생성되는 것이 아닌 빈 파일이 생성되고 write 하는 방식)
// 외부 저장소 경로 가져오기
val externalStoragePath = Environment.getExternalStorageDirectory().path
// 파일 사이즈 추출
val file = File("$externalStoragePath/$folderName/$sendData")
val fileSize = file.length()
val fileSizeString = fileSize.toString()
파일 사이즈를 추출해 주기적으로 이전 사이즈랑 비교하여 사이즈가 동일할 경우에
Socket(serverIp, serverPort).use { socket ->
PrintWriter(socket.getOutputStream(), true).use { writer ->
DataInputStream(FileInputStream(mFile)).use { dis ->
val buffer = ByteArray(1024)
var read: Int
while (dis.read(buffer).also { read = it } > 0) {
socket.getOutputStream().write(buffer, 0, read)
socket.getOutputStream().flush()
}
}
}
}
소켓을 연결하고, 해당 녹음 파일을 서버로 전송한다.
통화를 받자마자 자동으로 녹음을 시작하는 방식은 구현할 수 없었지만 UI를 통해 사용자에게 액션을 요청하는 방식으로 문제를 해결할 수 있었다. 대표님을 포함한 여러 상사분들께 시연을 직접 보여드리고 이 방식이 채택될 수 있었다.