Insights·2026-08-07

카카오톡 로컬 DB는 어떻게 읽고, 왜 맥에서만 되는가

카카오톡에는 다른 프로그램이 대화를 읽어 갈 수 있는 공식 API가 없다. 그래서 대화를 코드로 다루려면 맥용 카카오톡이 자기 앱 폴더에 남겨 둔 데이터베이스 파일을 직접 여는 수밖에 없다. 이 파일은 SQLCipher로 통째로 암호화돼 있고, 열쇠는 이 맥의 하드웨어 고유번호와 내 카카오 계정 번호를 재료로 만들어진다. 하드웨어 고유번호를 읽는 방식도, 앱이 파일을 두는 위치도, 그 폴더에 접근할 때 요구되는 권한 체계도 전부 macOS 것이라 이 파이프라인은 맥에서만 돈다. 게다가 cron으로 돌리면 권한 창을 띄우지 못해 조용히 멈춘다. 여기까지 넘기고 나면 나머지는 평범하다. 방별로 새 메시지만 뽑아 요약하고, Slack으로 보내고, Slack에서 봇을 부르면 AI가 이슈 본문을 써 Jira에 등록한다.

카카오톡 DB 복호화는 맥에서만 성립한다 — 복호화 CLI와 LaunchAgent 등록 명령, cron과 LaunchAgent 대비를 담은 요약 도식
cron은 권한 대화상자를 못 띄워 조용히 멈춘다. LaunchAgent는 로그인 세션 안에서 돌아 프롬프트를 띄울 수 있다.

파이프라인은 네 단계다

먼저 전체 모양부터 보자. 카카오톡 오픈채팅방의 대화를 매일 아침 요약해서 Slack 채널에 올리고, 그 Slack 스레드에서 AI가 이슈 본문을 써서 Jira에 티켓으로 등록한다. 단계로 쪼개면 넷이다. 하나, 카카오톡에서 대화를 읽는다. 둘, 방별로 요약한다. 셋, Slack에 보낸다. 넷, Slack에서 Jira 이슈를 만든다.

이 중 둘·셋·넷은 어렵지 않다. 요약은 LLM에 텍스트를 넘기면 되고, Slack은 웹훅 주소 하나로 글을 올릴 수 있고, Jira는 공식 REST API가 있다. 셋 다 문서화된 정식 경로다.

문제는 첫 단계다. 카카오톡에는 다른 프로그램이 '이 방의 대화를 달라'고 물어볼 수 있는 공식 창구가 없다. 카카오가 제공하는 개발자 API는 로그인, 메시지 보내기, 공유하기 같은 내보내기 방향이지, 내 대화를 읽어 오는 방향이 아니다. 그래서 첫 단계에만 나머지 셋을 합친 것보다 많은 공수가 들어간다.

대화를 코드로 읽는 유일한 입구

읽을 방법이 정말 없는지부터 따져 보자. 사람이 손으로 하는 길은 있다. 카카오톡 대화방에서 '대화 내보내기'를 하면 txt나 csv 파일이 나온다. 실제로 이 방식으로 뽑아 둔 파일도 쓰고 있다. 하지만 매일 아침 자동으로 도는 파이프라인에는 못 쓴다. 사람이 방마다 눌러야 하기 때문이다.

남는 길은 하나다. 카카오톡이 이미 내 컴퓨터에 저장해 둔 대화 데이터를 직접 여는 것이다. 맥용 카카오톡은 받은 메시지를 앱 컨테이너 안 데이터베이스 파일에 쌓아 둔다. 컨테이너란 macOS가 앱마다 하나씩 만들어 주는 전용 폴더다. 앱은 원칙적으로 자기 컨테이너 안에서만 논다.

여기서 짚고 갈 것이 있다. 이건 남의 데이터를 훔쳐보는 이야기가 아니다. 내 맥에 내가 로그인해서 받은 내 대화를, 그 맥의 주인인 내가 읽는 것이다. 그래도 방식 자체는 카카오가 열어 준 문이 아니라 뒷문이므로, 카카오가 저장 방식을 바꾸면 언제든 끊긴다는 전제를 깔고 써야 한다.

암호화된 DB의 열쇠는 어떻게 만들어지는가

암호화된 카카오톡 DB의 두 겹 벽과 열쇠가 만들어지는 재료를 정리한 도식

컨테이너 폴더를 열어도 곧바로 대화가 보이지는 않는다. 두 겹의 벽이 있다.

첫 겹은 파일 이름이다. 대화 DB는 kakaotalk.db 같은 알아볼 수 있는 이름이 아니라 78자리 16진수 문자열이다. 게다가 이 이름은 기기마다 다르다. 계정 번호와 기기 식별자를 섞어 계산해 낸 값이기 때문이다. 즉 폴더를 열어 봐도 어느 파일이 대화 DB인지 이름만으로는 모른다.

둘째 겹은 암호화다. 파일 자체가 SQLCipher로 통째로 암호화돼 있다. SQLCipher는 SQLite 데이터베이스를 파일 단위로 암호화해 주는 확장이다. 일반 sqlite3 명령으로 열면 '이건 데이터베이스 파일이 아니다'라는 에러가 난다. 암호를 알고 sqlcipher 명령으로 열어야 한다.

그 암호는 어디서 오나. 사용자가 정한 비밀번호가 아니다. 두 조각을 재료로 프로그램이 계산해 낸다. 하나는 이 맥의 하드웨어 고유번호이고, 다른 하나는 내 카카오 계정의 내부 번호다. 이 둘을 섞어 PBKDF2라는 표준 키 파생 함수에 십만 번 돌려서 최종 열쇠를 만든다. PBKDF2는 짧은 재료를 반복 계산으로 늘려 무차별 대입을 비싸게 만드는 표준 절차다.

여기 재미있는 대목이 하나 있다. 계정 번호가 설정 파일에 그대로 적혀 있지 않다. 해시, 즉 원래 값을 알아볼 수 없게 뭉갠 결과만 남아 있다. 그래서 0부터 숫자를 차례로 넣어 보며 같은 해시가 나오는 값을 찾는다. 계정 번호가 그리 크지 않은 정수라 이 방식이 현실적인 시간 안에 끝난다.

이 구조는 내가 알아낸 것이 아니다. blluv라는 개발자가 공개한 gist와 kakaocli 프로젝트가 먼저 정리해 둔 것이고, 내 코드도 그 두 출처를 주석에 명시하고 있다. 그래서 이 글에서도 구조와 제약까지만 설명하고, 그대로 복사해 쓸 수 있는 키 파생 코드는 싣지 않는다. 필요하면 원 출처를 보면 된다.

왜 맥에서만 되는가

이 파이프라인이 맥에서만 성립하는 네 가지 macOS 의존 요소를 정리한 도식

이 파이프라인을 윈도우나 리눅스로 옮길 수 있느냐는 질문을 자주 받는다. 지금 코드로는 못 돌린다. 이 구현이 macOS에 묶이는 지점이 넷이다.

첫째, 열쇠의 재료인 하드웨어 고유번호가 macOS 것이다. ioreg라는 macOS 명령으로 읽는 IOPlatformUUID인데, 윈도우에는 같은 값이 없다.

둘째, 파일이 놓이는 위치가 macOS 샌드박스 구조다. ~/Library/Containers 아래 앱 번들 아이디로 된 폴더가 있고 그 안에 DB가 있다. 윈도우 카카오톡은 저장 위치도 파일 구조도 다르다.

셋째, 접근 권한 체계가 macOS의 TCC다. TCC는 어떤 프로그램이 다른 앱의 데이터에 손대려 할 때 사용자에게 허락을 묻는 macOS 개인정보 보호 장치다. 다른 운영체제에는 대응물이 없다.

넷째, 모바일은 애초에 대상이 아니다. iOS와 안드로이드는 앱 데이터에 다른 앱이 접근하는 것을 운영체제가 원천 차단한다. 즉 이 구현이 성립하는 조건은 맥용 카카오톡이다.

오해 하나는 정정해 둔다. 윈도우에서 대화를 못 읽는다는 뜻이 아니다. 윈도우 카카오톡은 대화를 %LocalAppData% 아래 chat_data 폴더에 .edb 파일로 쌓고 AES로 암호화하는데, 그 키 파생 방식은 국내 포렌식 연구로 이미 공개돼 있다(blog.system32.kr 등). 즉 윈도우 이식은 막힌 문제가 아니라 키 파생을 그쪽 규칙으로 다시 짜는 작업이다. 이 글이 맥만 다루는 건 내 파이프라인이 맥에서 돌기 때문이지 윈도우가 닫혀 있어서가 아니다.

실무적으로 남는 제약은 하나다. 파이프라인을 돌릴 맥 한 대가 늘 켜져 있어야 한다.

cron으로는 안 된다 — LaunchAgent여야 하는 이유

매일 아침 도는 작업이니 cron에 걸면 될 것 같다. 실제로 처음에는 그렇게 했고, 실패했다. 실패 방식이 고약해서 따로 적어 둔다.

에러가 나지 않는다. 거부 메시지도 없다. 그냥 그 자리에서 멈춘다. 원인은 TCC다. 카카오톡 컨테이너를 읽으려면 전체 디스크 접근 권한이 필요한데, macOS는 이 권한을 물을 때 화면에 대화상자를 띄운다. 그런데 cron은 백그라운드 데몬이라 사용자 화면에 창을 띄울 방법이 없다. 그래서 물어보지도 못하고, 거부 처리도 아니고, 응답을 기다리며 매달린다.

해결은 실행 주체를 바꾸는 것이다. cron 대신 LaunchAgent를 쓴다. LaunchAgent는 로그인한 사용자 세션 안에서 도는 macOS 기본 스케줄러다. 사용자 세션 안이므로 권한 창을 띄울 수 있고, 한 번 허락받으면 그 뒤로는 조용히 돈다.

여기서 파생되는 함정이 하나 더 있다. 권한은 실행 파일 단위로 붙는다. sqlcipher 명령이 카카오톡 컨테이너를 직접 읽게 하면 sqlcipher 바이너리에도 따로 권한을 줘야 하고, Homebrew가 sqlcipher를 업데이트할 때마다 권한 프롬프트가 되살아난다. 그래서 파이썬이 컨테이너에서 파일을 /tmp로 복사한 뒤, sqlcipher는 그 사본만 건드리게 했다. 권한을 가진 주체를 하나로 좁힌 것이다.

복호화된 평문 DB도 신경 써야 한다. /tmp에 그냥 두면 기본 권한상 같은 기기의 다른 프로세스가 읽을 수 있다. 대화 전문이 담긴 파일이므로 만들자마자 소유자 전용으로 권한을 조인다.

복호화에 필요한 CLI와 LaunchAgent 등록
# SQLCipher CLI — brew autoremove로 지워지면 파이프라인이 통째로 멈춘다
brew install sqlcipher

# cron이 아니라 LaunchAgent로 등록한다
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.edb.kakao-briefing.plist
launchctl list | grep kakao-briefing

읽고 나서 — 새 메시지만 뽑고 방별로 요약한다

여기까지 오면 평문 SQLite 파일 하나가 손에 남는다. 이제부터는 평범한 데이터 처리다.

전체를 매번 요약하면 비용도 시간도 감당이 안 되므로, 방마다 마지막으로 처리한 메시지 번호를 기록해 두고 그 뒤에 온 것만 뽑는다. 흔히 하이워터마크라고 부르는 방식이다. 주말에 실행이 걸러져도 메시지가 유실되지 않도록 조회 창은 넉넉히 잡아 두고, 중복은 메시지 번호가 막는다.

메시지가 다 같은 종류는 아니다. DB에는 텍스트뿐 아니라 이미지, 광고, 시스템 알림이 전부 같은 테이블에 섞여 있고 종류마다 번호가 붙어 있다. 텍스트와 답글·링크 공유만 걸러 넣고 나머지는 버린다. 이 필터를 빼면 요약 프롬프트가 광고 문구로 채워진다.

요약은 방을 하나씩 따로 호출한다. 처음에는 전체 방을 한 번에 묶어 보냈는데, 메시지가 폭주한 방 하나가 타임아웃에 걸리자 그 호출에 묶인 나머지 방 요약이 통째로 사라졌다. 방별 루프에 개별 예외 처리를 두면 한 방이 죽어도 나머지는 살아 남는다. 방마다 최근 메시지 수 상한도 걸어 둔다.

비용도 갈라 뒀다. 일반 오픈채팅방 요약은 로컬에서 도는 작은 모델이 처리하고, 정확성이 중요한 사업 관련 방과 일정 추출만 상위 모델에 맡긴다. 실패하면 자동으로 상위 모델로 넘어간다.

Slack에서 Jira로 — AI가 쓰는 건 PRD가 아니라 이슈 본문이다

요약이 나오면 Slack 웹훅으로 해당 채널에 올린다. 웹훅은 Slack이 발급해 주는 주소 하나로, 그 주소에 글자를 POST하면 채널에 글이 올라가는 가장 단순한 연동이다.

여기서 구현 함정 하나. 처음에는 요약 마크다운에서 제목 줄을 정규식으로 찾아 방 본문을 잘라 Slack에 보냈다. 그런데 텔레그램 출력을 평문으로 바꾸면서 제목 기호가 사라지자 이 파싱이 조용히 깨졌다. 지금은 요약 단계에서 방 이름을 키로 하는 JSON 파일을 따로 떨어뜨리고, Slack 전송은 그 JSON을 읽는다. 표시 형식이 바뀌어도 데이터 경로는 안 흔들린다.

Slack에 올라온 뒤부터가 두 번째 봇의 영역이다. 이 봇은 Socket Mode로 붙는다. 서버에 공개 주소를 열지 않고 Slack 쪽으로 연결을 걸어 두는 방식이라, 사내 네트워크나 개인 서버에서도 방화벽을 뚫을 필요가 없다.

봇을 멘션하면 그 메시지를 LLM에 넘겨 구조화된 JSON을 받는다. 필드는 이슈를 만들 것인지, 어느 프로젝트인지, 유형과 우선순위는 무엇인지, 그리고 제목과 본문이다. 본문은 증상·재현 방법·기대 결과 세 덩어리로 쓰게 시킨다.

여기서 이름을 정확히 하고 싶다. 흔히 이 단계를 'AI가 PRD를 쓴다'고 부르지만, 실제 산출물은 별도의 기획 문서가 아니라 이슈 본문이다. 제품 요구사항 문서 한 편을 만드는 게 아니라, 개발자가 바로 손댈 수 있는 티켓 한 장을 채우는 일이다. 이 구분을 흐리면 기대치가 어긋난다.

판단은 확신도로 자른다. 0.8을 넘으면 바로 Jira에 만들고 스레드에 링크를 답글로 남긴다. 0.5에서 0.8 사이면 사람에게 만들지 물어본다. 0.5 미만이면 로그만 남기고 무시한다. 같은 메시지가 두 번 처리되지 않도록 메시지 타임스탬프로 중복을 막는다.

사람이 남는 자리가 정확히 여기다. 반복되는 취합·정리·작성은 기계가 하고, 사람은 애매한 것을 판정하고 만들지 말지를 정한다.

카나나가 B2B로 오면 이 층은 사라진다

이 파이프라인의 첫 단계는 공식 창구가 없어서 만든 우회로다. 우회로의 수명은 정문이 열리는 날까지다.

카카오는 이미 그 방향을 보여 줬다. 2026년 4월 23일 서울 코엑스 월드IT쇼에서 카카오는 AI 브랜드 카나나의 '오늘의 브리핑'을 공개했다. 사용자의 과거 대화를 분석해 중요한 일정과 기념일, 할 일을 요약해 주는 기능이다. 약속 장소를 정하는 대화에서 맛집을 추천하거나, 생일 대화를 감지해 선물하기와 연동하는 맥락 인식 기능도 함께 소개됐다. 내가 DB를 복호화해 만든 것과 목적이 같은 물건이다.

다만 발표된 범위는 개인용이다. B2B나 기업용에 대한 언급은 없었다. 그리고 지금 필요한 것은 개인의 하루 브리핑이 아니라, 조직 채널의 대화가 업무 티켓으로 이어지는 층이다.

그 층이 열릴 여지는 있다고 본다. 카카오는 2026년 7월 카나나-2 경량 모델 네 종을 오픈소스로 공개하면서 도구 호출 능력을 강조했다. 도구 호출이란 모델이 스스로 외부 시스템의 기능을 불러 쓰는 것을 말한다. 대화를 읽고 판단해서 다른 시스템에 등록하는 일이 정확히 그 능력의 용도다.

그날이 오면 내 복호화 코드는 폐기된다. 그게 정상이다. 우회로는 정문이 없을 때만 값이 있고, 정문이 생기면 미련 없이 버리는 게 맞다. 자동화 자산을 만들 때 이 점을 처음부터 계산에 넣는 편이 낫다. 어느 층이 임시 우회로이고 어느 층이 오래 갈 자산인지 구분해 두면, 플랫폼이 움직였을 때 갈아엎을 범위가 명확해진다.

이 파이프라인에서 오래 갈 자산은 복호화가 아니라 뒷단이다. 방별로 요약을 나누는 방식, 확신도로 자동과 확인을 가르는 게이트, 중복을 막는 키 설계는 입력이 카카오톡이든 공식 API든 그대로 쓴다.