블로그 이미지
자유로운설탕

calendar

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

Notice

'분류 전체보기'에 해당되는 글 71

  1. 2026.03.02 SQL 이해해보기 #05 - 책 대여 프로그램 만들기
  2. 2026.03.02 SQL 이해해보기 #04 - 정교하게 타겟하기1
  3. 2026.03.02 SQL 이해해보기 #03 - 관계에 대해 살펴보기
  4. 2025.12.21 SQL 이해해보기 #02 - 쿼리가 만들어지는 과정1
  5. 2025.12.21 SQL 이해해보기 #01 - 데이터베이스와 테이블 이해하기
  6. 2025.12.21 SQL 이해해보기 - 들어가면서
  7. 2025.11.15 일본어와 중국어의 차이 11편 - 이런저런 마무리
  8. 2025.11.15 일본어와 중국어의 차이 10편 - 시험
  9. 2025.09.28 일본어와 중국어의 차이 9편 - 노래 부르기4
  10. 2025.09.28 일본어와 중국어의 차이 8편 - 입력 방식2
  11. 2025.08.24 일본어와 중국어의 차이 7편 - 다양한 장르의 표현들10
  12. 2025.08.10 일본어와 중국어의 차이 6편 - 한자의 뉘앙스12
  13. 2025.07.29 일본어와 중국어의 차이 5편 - 한자의 발음10
  14. 2025.07.24 일본어와 중국어의 차이 4편 - 외국어의 표현14
  15. 2025.07.15 일본어와 중국어의 차이 3편 - 어순2
  16. 2025.07.06 일본어와 중국어의 차이 2편 - 글자18
  17. 2025.07.05 일본어와 중국어의 차이 1편 - 한자의 차이8
  18. 2025.07.05 일본어와 중국어의 차이 - 들어가면서7
  19. 2022.09.04 [책소개] 버그 정글을 헤쳐 가기 위한 테스터 지침서
  20. 2021.09.15 보안 책 출간이 되었습니다.
  21. 2020.05.10 구글로 공부하는 보안 - 15교시(악성코드)1
  22. 2020.05.04 ハッピーエンド(해피 앤드) - back number
  23. 2020.04.18 慢慢喜欢你 - 莫文蔚 (만만시환니 - 천천히 당신을 좋아하기)
  24. 2019.10.06 구글로 공부하는 보안 - 14교시(모니터링 문제)
  25. 2019.08.25 구글로 공부하는 보안 - 13교시(리버싱과 포렌식)
  26. 2019.07.07 구글로 공부하는 보안 - 12교시(자동화 잡)
  27. 2019.05.20 구글로 공부하는 보안 - 11교시 (스캐너 vs 수동 테스트)
  28. 2019.05.06 구글로 공부하는 보안 - 10교시 (보안 설계 문제 security by design)
  29. 2019.05.01 구글로 공부하는 보안 - 9교시 (하드닝 Hardening)
  30. 2019.03.24 구글로 공부하는 보안 - 8교시 (API)
2026. 3. 2. 18:47 프로그래밍

앞에서 얘기했듯이 SQLite 라는 로컬 전용 데이터베이스를 이용하여 책 대여 프로그램의 내부를 설계해 보자. 먼저 얘기했듯이 실제 설치를 해서 실습을 한다고 했는데 SQLite 를 사용하는 방법은 크게는 3가지 정도가 있다고 볼 수 있다. 첫 번째는 SQLite 실행 파일만 다운로드 해서 윈도우의 CMD 창이나 맥의 Terminal 에서 실행하는 방법이 있고(맥은 사실 이미 SQLite3 가 기본으로 설치되어 있긴 한다고 한다). 두 번째는 파이썬이나 자바, .NET 같은 프로그램을 이용해서 호출하는 건데 보통 이 경우는 기본 패키지에 포함되어 있거나, 추가로 패키지를 설치해 쓴다고 보면 될 듯 하고, 마지막 방법이 SQL 쿼리를 도와주는 "DB Browser for SQLite" 같은 DB 클라이언트 프로그램을 사용하는 거다.

사실 이런 SQLite 같은 작은 로컬 기반의 디비가 아닌 경우는 위의 3가지 중 어느 방법을 쓰더라도 보통 SQL 서버 자체를 별도로 구축해야 하는 경우를 빼 먹을 수는 없겠지만, SQLite 는 특별하게 비 설치 형으로 아주 작은 형태의 로컬 환경에서의 디비 제공을 지향하는 프로그램이기 때문에 위와 같이 간단한 방식이 가능하다고 보면 될 것 같다. 다른 종류의 데이터베이스와의 차이는 우선 여기서 SQLite 이해한 후 다음 얘기에서 한번 비교하여 풀어보자. .

사실 데이터베이스의 사용을 얘기하려면 위의 3가지 방법을 다 이해하는 게 맞긴 해서 어떻게 할지 고민 하다가, 우선은 앞의 SQLite 웹 사이트에서 실습 한 것과 비슷한 모습을 보여주는 "DB Browser for SQLite" 를 사용해 실습 해 보여주고, 뒤에 파이썬으로 프로그램을 만들어 조회를 해보거나, 마지막에 CMD 나 Terminal 창에서 실제 명령어를 날려 조회하는 방식으로 움직이는 부분을 살짝 보여주고 마무리 하려 한다. 사실 요즘 엄청 핫 하게 얘기 되고 있는 AI Agent 들도 결국은 저 CMD 나 나중에 Nosql 에서 얘기할 API 를 기반으로 복잡하게 얽혀 AI 모델을 중심으로 마치 하나의 프로그램 같이 생각 되도록 움직이는 것에 가깝다고 보기 때문에 명령어 방식도 꼭 한번 보여주고 넘어가 보려 한다. 앞에서 얘기했듯이 IT 세상에서는 이해가 안되고 신기하게만 보이면 안되기 때문이다. 

참고로 이젠 실습이기 때문에 굳이 한 땀 한 땀 따질 필요가 적고, 아는 지식을 기반으로 AI 검색 엔진을 사용해서 기본 쿼리 들을 만들게 할 건데, 이 글의 진행에서는 무료로 사용 가능한 Gemini Flash 버전을 비 로그인 상태에서 사용하고 있으니 참고 부탁 드린다.

앞에서 윈도우, 맥 상에서 모두 시연이 가능하도록 한다고 약속을 했으니, 우선 DB Browser for SQLite를 2개의 OS에서 설치하는 방법부터 보자. 일단 어느 OS 이던 구글에서 "db browser for SQLite" 를 우선 검색해 보자. 가장 처음에 나오는 "https://sqlitebrowser.org" 사이트를 클릭 해서, "Download" 페이지로 가보자. 

 

 



[윈도우 용 DB Browser for SQLite 설치 및 쿼리 실행 해보기]

먼저 윈도우의 경우는 실제 프로그램으로 설치, zip 파일을 해제해서 실행, 하나의 파일로 된 포터블 실행 파일 버전 3가지가 있다. 어느 걸 설치해도 무방하지만 혹시 직접 설치하는 게 좀 그렇다면, "DB Browser for SQLite - .zip (no installer) for 64-bit Windows" 를 다운로드 해서 아래와 같이 적절한 폴더에 압축을 풀어보자(개인적으로 c:\SQL 폴더를 만들어서 거기에 압축을 풀었다).

<Win - 압축 풀기>




이후 DB Browser for SQLite.exe 파일을 실행하자. 아래와 같은 빈 프로그램 화면이 나오면 성공했다고 보면 된다.

<Win - SQLite 실행>




처음이니까 쿼리를 만드는 데까지 자세히 설명하고, 이후부터는 원래 모드로 쿼리 위주로 다시 설명을 들어가려 한다. 우선 "새 데이터베이스" 버튼을 클릭해 보자. 컴퓨터에서 돌아가는 모든 프로그램들은 결국은 파일로 데이터를 저장하게 된다(물론 가끔 악성 코드 같은 경우는 메모리 상에서만 돌아가다가 없어지는 경우도 있지만, 메모리도 결국은 0과 1을 저장하는 휘발성 장소이기 때문에. 데이터의 구조를 저장한 다는 측면에서는 디스크에 저장되는 파일과 특별히 다르지는 않다고 본다. 파일이라는 개념 또한 가상의 0과 1의 배열을 이용하여 내용을 저장하기 위한 OS의 규칙이기 때문이다). 여하튼 현재 실행 폴더가 나오면 "mytest" 이름으로 저장해 보자.

<Win - 새 데이터베이스 만들기>

 



이후 "테이블 정의 변경"이라는 창이 나오는데(데이터베이스를 만들었으니 테이블을 하나 만들어 보라는 뉘앙스다), 이건 그냥 우선 취소를 해서 닫자. UI 를 이용해서, 테이블 생성하기 같은 버튼을 눌러서 테이블을 만들 수도 있겠지만, 우린 이미 쿼리를 배웠으니 그냥 바로 쿼리로 얘기를 해보도록 하자. 

위 쪽 메뉴에서 "SQL 실행" 탭을 누르면, 우리가 앞 글에서 봤던 웹 상의 편집기 같은 쿼리를 입력할 수 있는 메뉴가 나온다. 우리가 하나의 파일 형태의 데이터베이스를 만들었고, 이 SQLite 편집기의 특성 상 기본적으로 하나의 데이터베이스를 대상으로 작업하도록 되어있으니 앞으로의 모든 행동은 현재의 mytest 데이터베이스에 저장이 될 것이다(실제 프로그램이 실행된 폴더를 확인해 보면 "mytest.db" 라는 파일이 생성되어 있다. 이 파일을 나중에 다른 곳으로 옮기면 앞으로 작업하는 모든 데이터가 다 같이 옮겨가게 된다).

<Win - 쿼리 입력기>





그럼 테스트 테이블을 하나 만들어 보자. AI 한테 부탁해서 일련번호가 들어가는 과자 테이블을 하나 만들었자.

CREATE TABLE snacks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL
);



전체에 대해서 명령을 날리려면, 입력한 내용을 아무 것도 선택하지 말고 F5키를 누르거나 위의 프린트 아이콘 오른쪽 옆에 있는 재생 모양의 화살표를 누르자. 이후 밑을 보면 아래와 같이 쿼리가 잘 실행 됐다는 결과 텍스트가 나온다. 

<Win - 테이블 만들기>




여기서 왼쪽에 있는 "데이터베이스 구조" 탭을 클릭해 보면 아무것도 없던 테이블 영역에 2개의 테이블이 생긴 것을 볼 수 있다. 컬럼의 데이터 형이나 속성들을 비주얼 하게 펼쳐서 볼 수도 있다(혹시나 sqlite_sequence 테이블이 왜 생겼는지 궁금하다면 마찬가지로 궁금해 찾아봤는데, AUTOINCREMENT 속성 땜에 각 테이블 별로 자동 증가하는 id 값이 현재 몇 번인지 저장해 두는 용도로 SQLite 에서 쓰는 내부 테이블이라고 한다).

<Win - 테이블 생성 정보>





다시 쿼리 쪽으로 가서 이번엔 과자 이름들을 넣어보자, 앞에서 설명했듯이, id 는 자동 증가되기 때문에 대상 컬럼 이름에서 빠져있다.

INSERT INTO snacks (name) VALUES ('새우깡');
INSERT INTO snacks (name) VALUES ('초코송이');
INSERT INTO snacks (name) VALUES ('포카칩');



마찬가지로 이제 굳이 촌스럽게 "데이터베이스 구조" 탭으로 가지 않아도 앞에서 쿼리 설명할 때처럼 머리 속으로 논리적으로 상상을 해보자. 네모난 "snacks" 테이블을 하나 만들고, 데이터 3줄을 넣었구나 하고 말이다.

<Win - 데이터 입력>




마지막 실습으로, 아래의 조회 쿼리를 마찬가지로 실행해 보면, 우리가 상상한 대로 데이터가 들어 있다는 것을 보여주게 된다. 매번 비어있던 가운데 공간이 예상 했던 대로 데이터 값이 조회 시 출력 되는 공간 이였었다.

SELECT * FROM snacks;

 

<Win - 데이터 조회>




하나 주의할 점은 마치 엑셀 작업한 내용을 저장하지 않음 날라가듯, 프로그램 종료 전에 저장을 안 하면 모든 내용이 없어지게 된다. "파일 > 변경사항 저장하기", "파일 > 프로젝트 저장하기(이건 현재 입력한 쿼리라든지 창 배치 등을 저장해 준다고 한다)"로 저장해 주거나, 종료 할 때 저장을 묻는 경우 모두 저장을 해주자. 간단히는 엑셀처럼 메모리 상에서 작업하다가 파일로 저장된다고 생각해도 무방할 듯 싶고, 조금 데이터베이스 입장으로 얘기로 하자면 트랜젝션(Transaction-일련의 전체 셋으로 꼭 성공해야 되는 쿼리 작업 단위)의 개념으로 생각해서 "rollback" 가능한 상태로 메모리 상에 존재하다가, 작업이 완전히 끝나서 "commit" 됐다고 보면 될 것 같다. 약간 SQLite 는 완전 파일 기반으로 표현하므로, 아마 해당 DB Browser 프로그램이 워드나 엑셀 같은 데이터의 "저장" 이라는 용어로 쉽게 다가가도록 표현된 듯 싶긴 하다. 

자 그럼 테스트를 완료하고, 다시 창을 띄우게 되면 다시 빈 공간만 보이게 되는데(최근 작업 파일을 가져오는 옵션이 있는지는 모르겠다), 커스터마이즈한 작업 공간을 복구하고 싶으면 "파일 > 프로젝트 열기"를 하여 "mytest.sqbpro" 파일을 읽어오고, 아까 저장한 데이터베이스를 가져와 다른 작업을 하고 싶다면 "파일 > 데이터베이스 열기"를 이용해서 아까 저장한 mytest.db를 가져와 보자. 해당 상태로 기존 만들었던 테이블과 데이터가 그대로 보이게 된다면, 앞으로의 실습 준비는 끝났다고 보면 될 듯 싶다.






[맥 용 DB Browser for SQLite 설치 및 쿼리 실행 해보기]

이번엔 맥북을 보자. 위의 윈도우 섹션은 당연히 안 보았으리라 생각하고 비슷하게 반복적으로 얘기를 적으니 양해 바란다. 마찬가지로 해당 페이지로 이동 해서, 다운로드 버튼을 눌러서 다운로드 후 프로그램을 더블 클릭해 어플리케이션으로 등록해 주자.

<MAC - 프로그램 설치>



이후 DB Browser for SQLite.exe 파일을 실행하자. 아래와 같은 빈 프로그램 화면이 나오면 성공했다고 보면 된다.DB Browser for sqliteDB Browser for sqliteDB Browser for sqlite 실행

<MAC - DB Brwoser for SQLite 실행>





처음이니까 쿼리를 만드는 데까지 자세히 설명하고, 이후 부터는 원래 모드로 쿼리 위주로 다시 설명을 들어가려 한다. 우선 "New Database" 버튼을 클릭해 보자. 컴퓨터에서 돌아가는 모든 프로그램들은 결국은 파일로 데이터를 저장하게 된다(물론 가끔 악성 코드 같은 경우는 메모리 상에서만 돌아가다가 없어지는 경우도 있지만, 메모리도 결국은 0과 1을 저장하는 휘발성 장소이기 때문에 디스크에 저장되는 파일과 특별히 다르지는 않다고 본다. 파일이라는 개념 또한 가상의 0과 1의 배열을 이용하여 내용을 저장하기 위한 OS의 규칙이기 때문이다). 폴더는 "Documents"로 하고 "mytest" 이름으로 저장해 보자.

<MAC - 새 데이터베이스 만들기>

 



이후 "Edit table definition"이라는 창이 나오는데(데이터베이스를 만들었으니 테이블을 하나 만들어 보라는 뉘앙스다), 이건 그냥 우선 취소를 해서 닫자. UI 를 이용해서, 테이블 생성하기 같은 버튼을 눌러서 테이블을 만들 수도 있겠지만, 우린 이미 쿼리를 배웠으니 그냥 바로 쿼리로 얘기를 해보도록 하자. 

위 쪽 메뉴에서 "Execute SQL" 탭을 누르면, 우리가 앞 글에서 봤던 웹 상의 편집기 같은 쿼리를 입력할 수 있는 메뉴가 나온다. 우리가 하나의 파일 형태의 데이터베이스를 만들었고, 이 SQLite 편집기의 특성 상 기본적으로 하나의 데이터베이스를 대상으로 작업하도록 되어있으니 앞으로의 모든 행동은 현재의 mytest 데이터베이스에 저장이 될 것이다(실제 저장한 Documents 폴더를 보면 "mytest.db" 라는 파일이 생성되어 있다. 이 파일을 나중에 다른 곳으로 옮기면 앞으로 작업하는 모든 데이터가 다 같이 옮겨가게 된다).

<MAC - 쿼리 입력기>





그럼 테스트 테이블을 하나 만들어 보자. AI 한테 부탁해서 일련번호가 들어가는 과자 테이블을 하나 만들었다.

CREATE TABLE snacks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL
);



전체에 대해서 명령을 날리려면, 입력한 내용을 아무 것도 선택하지 말고 F5키를 누르거나 위의 프린트 아이콘 오른쪽 옆에 있는 재생 모양의 화살표를 누르자. 이후 밑을 보면 아래와 같이 쿼리가 잘 실행됐다는 결과 텍스트가 나온다. 

<MAC - 테이블 생성>



여기서 왼쪽에 있는 "Database Structure" 탭을 클릭해 보면 아무것도 없던 테이블 영역에 2개의 테이블이 생긴 것을 볼 수 있다. 컬럼의 데이터 형이나 속성들을 비주얼 하게 펼쳐서 볼 수도 있다(혹시나 sqlite_sequence 테이블이 왜 생겼는지 궁금하다면 마찬가지로 궁금해 찾아봤는데, AUTOINCREMENT 속성 땜에 각 테이블 별로 자동 증가하는 id 값이 현재 몇 번인지 저장해 두는 용도로 SQLite 에서 쓰는 내부 테이블이라고 한다).

<MAC - 테이블 만든 후>





다시 쿼리 쪽으로 가서 이번엔 과자 이름들을 넣어보자, 앞에서 설명했듯이, id 는 자동 증가 되기 때문에 대상 컬럼 이름에 빠져있다.

INSERT INTO snacks (name) VALUES ('새우깡');
INSERT INTO snacks (name) VALUES ('초코송이');
INSERT INTO snacks (name) VALUES ('포카칩');



마찬가지로 이제 굳이 촌스럽게 "Database Structure" 탭으로 가지 않아도 앞에서 쿼리 설명할 때처럼 머리 속으로 논리적으로 상상을 해보자. 네모난 "snacks" 테이블을 하나 만들고, 데이터 3줄을 넣었구나 하고 말이다.

<MAC - 데이터 입력>





마지막 실습으로, 아래의 조회 쿼리를 마찬가지로 실행해 보면, 우리가 상상한 대로 데이터가 들어 있다는 것을 보여주게 된다. 매번 비어있던 가운데 공간이 예상 했던 대로 데이터 값이 조회 시 출력 되는 공간 이였었다.

SELECT * FROM snacks;

<MAC - 데이터 조회>




하나 주의할 점은 마치 엑셀 작업한 내용을 저장하지 않음 날라가듯, 프로그램 종료 전에 저장을 안하면 모든 내용이 없어지게 된다. "File > Write Changes", "File > Save Project(이건 현재 입력한 쿼리라든지 창 배치등을 저장해 준다고 한다)"로 저장해 주거나, 종료할때 저장을 묻는 경우 모두 저장을 해주자. 간단히는 엑셀 처럼 메모리 상에서 작업하다가 파일로 저장된다고 생각해도 무방할 듯 싶고, 조금 데이터베이스 입장으로 얘기로 하자면 트랜젝션(Transaction-일련의 전체 셋으로 꼭 성공해야 되는 쿼리 작업 단위)의 개념으로 생각해서 "rollback" 가능한 상태로 메모리 상에 존재하다가, 작업이 완전히 끝나서 "commit" 됬다고 보면 될 것 같다. 약간 SQLite 는 완전 파일 기반을 표현하므로, 아마 해당 DB Browser 프로그램이 워드나 엑셀 같은 "저장" 이라는 용어로 쉽게 다가가도록 표현된 듯 싶긴하다. 

자 그럼 테스트를 완료하고, 다시 창을 띄우게 되면 다시 빈 공간만 보이게 되는데(최근 작업 파일을 가져오는 옵션이 있는지는 모르겠다),, 커스터마이즈한 작업 공간을 복구하고 싶으면 "File > Open Project"를 하여 "mytest.sqbpro" 파일을 읽어오고, 아까 저장한 데이터베이스를 가져와 다른 작업을 하고 싶다면 "File > Open Database"를 이용해서 아까 저장한 mytest.db를 가져와 보자. 해당 상태로 기존 만들었던 테이블과 데이터가 그대로 보이게 된다면, 앞으로의 실습 준비는 끝났다고 보면 될 듯 싶다.




 



혹시나 나중에 MSSQL이나, Oracle, MySQL, PostgreSQL 등 여러가지 파일이 아닌 서버(서비스) 형태의 디비를 쓰더라도 마찬가지로 이런 비슷한 쿼리를 날릴 수 있는 SQL 클라이언트(SQL 서버에 쿼리를 날리는 고객이라서 SQL 클라이언트다)를 설치하거나, 클라우드라면 내부 또는 SAAS 서비스로 제공 되는 여러 툴 들을 이용해서 쿼리를 비슷하게 날리게 된다. 

나중에 얘기할 NoSQL 도 좀 더 개발 쪽 친화적이여서 모양만 낯설 뿐 비슷하다고 보면 된다. 다만 서버 형태의 데이터베이스들의 경우는 원격의 서비스 이기 때문에, 앞의 데이터베이스 파일 열기 같은 형태가 아니라 마치 우리가 웹 사이트에 로그인 할 때처럼 보통 ID, Password 를 넣어서, SQL 서버에 로그인 후 해당 세션을 유지하며 쿼리를 날리게 되고, 이 것을 정보를 입력하면 해당 클라이언트가 그런 자잘한 일들은 자동으로 해주게 된다. 

자 그럼 실습을 할 수 있는 환경은 준비가 됐으니 이젠 다시 쿼리의 세계로 여행을 떠나보자. 앞에서 얘기한 대로, 프로그램하고 연결은 안 되 있지만 데이터베이스와 쿼리를 이용해서 책 대여점 프로그램을 만들어 보자(뭐 책 대여점이라는 개념이 낯설다면, 돈을 주고 빌려주는 유료 도서관 시스템이라고 생각해 보자). 

시작하기 전에 잠시 프로그램의 세계를 엿보자면 어느 순간부터 프론트엔드, 백엔드로 경계를 나눠서 얘기하고 있었는데, 지금은 다시 어느 쪽이든 API 를 기준으로 한 비동기 프로그램이 대세가 되고, 프론트엔드도 점점 엄격한 객체지향의 세계로 변하게 되면서, 구경하는 서당 개의 입장에서 보면 두 개의 프로그램 스타일의 경계가 다시 무너지는 느낌도 들긴 하지만, 뭐 굳이 따지자면 데이터베이스 관련된 테이블과 쿼리의 설계는 백엔드 쪽에 속한다고 볼 수는 있을 것이다. 사실 디비를 중심으로 한 쿼리 중심으로 보면 외부의 프로그램 코드는 해당 쿼리들을 적절히 설계 후에 비즈니스 규칙에 맞춰서 쿼리 들을 UI 의 요청에 맞춰 적절히 호출하는 일을 주로 담게 된다.

물론 ORM(Object-Relational Mapping) 같은 방식으로, 구조적으로 디비를 정의해 User.find(1) 이런 식으로 쿼리를 자동으로 만들어주어 개발자 쪽에서 쿼리를 인지 못하게 할 수도 있지만 내부적으로는 결국 마지막에는 데이터베이스에 쿼리를 날리는 SQL 이 실행된다는 부분에서는 SQL 을 이해해야 한다는 결론에는 크게 다르진 않는 듯도 싶다. 음 뭐 나중에는 해당 부분의 번역 또한 AI 실시간으로 실행 시 해주게 되면 상황이 변하게 될 것은 같긴 하지만, 어쨌든 모든 결과의 실행은 실제로 데이터베이스 입장인 SQL 세상에서 일어나게 된다. 

요즘 핫한 OS 에서 돌아가는 AI Agent 들도 결국은 맨 마지막에서는 구닥다리 윈도우 API 나 CLI, 타 솔루션의 API 등을 호출해서 정교한 명령을 수행하게 될 테니까 말이다. 뭐 언젠가 AI 가 더 발전해서 세상의 모든 기술이 굳이 인간이 이해할 필요가 없는(아마 무생물인 AI 관점에서는 가독성에 치중된 꽤 비효율적인 인터페이스 일 수도 있을테니 말이다) 바이너리 같은 코드와 기술로 다 컨버전되어 바뀌어 버리기 전에는 이건 당분간 바뀌진 않을 것 같다(아마 현실 측면에서는 구현된 기술의 저작권 문제도 있을 거다). 아마도 그래서 이러한 미래의 레거시 후보가 될 뒷 세계의 모양을 아는 것이 현재 정확하게 AI 가 미치는 세상을 이해하는데 도움이 될 것 같긴 하다. 앞에서 얘기했지만 현재 얘기하는 SQL 도 그런 측면에서 이해하는데 의미가 있다. 다시 한번 의견을 말하자면 IT 쪽에서 기술이 이해하기 힘든 마법같이 보임 절대 안 된다고 생각한다. 뭐 약간의 발전을 위한 긍정적인 환타지는 허용되야 할테지만 말이다.

그럼 다시 현실로 돌아와서 책 대여점 프로그램에 어떤 게 필요할지 생각을 해보자. 자 우선 그전에 우리가 사용하고 있는 RDB(Relational Database) 가 뭔지 생각해 보자. 보통 복잡하게 얘기하긴 하는데 야매 입장에서 보면 RDB 가 처음 나왔던 시절은 디스크 등의 용량이 엄청 비쌌던 시절이다. 몇 십 메가의 하드가 주력이던 시절이니 말이다(WD Caviar 93044 같은 모델이 40MB 라고 한다). 그래서 그 당시 중복된 데이터를 저장한다는 것은 아마 엄청 사치스러운 접근법이였을테고, 기본적으로 중복되지 않은 데이터를 저장하는 것을 보통 정규화라는 조금 어려운 학술 표현으로 쓰는 것 같다. 

그래서 지금의 많은 RDB 테이블에서 볼 수 있는 상품 정보, 주문 정보 등의 테이블로 분리되면서 실제 길이가 긴 상품 명은 상품 테이블에만 저장되고 주문 정보 테이블에서는 상품 번호만 저장하는 식으로 중복 데이터를 최소화 하는 식으로 저장되었고, 주문 정보에서 상품 정보를 같이 보여주기 위해서 필연적으로 우리가 골치 아파하는 JOIN 키워드와 빠른 JOIN을 도와주는 인덱스라는 존재가 꼭 필요하게 됐다고 볼 수 있다(현재의 NoSQL이나 현대적인 RDB 설계의 측면에서는 이 부분을 역으로 접근했다고 생각해 보자). 

이 방식은 하나의 얘기치 않은 장점도 있는데, 중요한 키워드 데이터들이 수많은 테이블 중에 보통 한 군데에만 들어가므로(예를 들면 상품 가격은 상품 테이블에만 있거나 또는 상품 코드를 가진 가격 테이블에만 있을 수 있다), 중복된 데이터 중 특정 컬럼 데이터의 업데이트를 놓쳐서 생기는 무결성의 깨짐 부분을 방지하는 효과를 가져온다. 또한 일반적으로 단일 서버 기준으로 데이터를 저장하므로 아무래도 무결성을 유지하면서도 빠르다. 

NoSQL 은 JOIN 같은 장점과 무결성을 조금은 포기하면서 서버를 병렬적으로 늘려 지금의 AI가 나온 빅데이터를 가능케 한 접근 방식이라고 보면 될 것 같고, 요즘은 두 개를 합쳐 다수의 서버로 속도와 무결성을 유지하려 하는 NEWSQL 이라는 서버군 들도 생겨나고 있다고 한다(설명을 보면 약간 NoSQL와 정해진 서버 들까지의 투표로 진위를 가리는 기업 용 블록체인 장부가 합쳐진 느낌이라고 할까 싶다). 여하튼 뭐 이런 확장된 부분들은 관심이 있다면 직접 해당 서버들을 사용하거나 구축하면서 장 단점을 느껴보면 되지 않을까 싶다.


 



자 그럼 중복된 데이터를 저장 안 하려 하는 RDB 의 특징을 생각하면서 책 대여 프로그램의 뒷 편 설계를 해보자. 

<책 대여 하기>




먼저 책을 저장하는 테이블이 하나 있음 좋겠다. 다른 테이블과 조인할 때 쓸 수 있는 고유 일련 번호는 당연히 있어야 하고, 책 이름도 있어야 한다. 대여 가격도 포함해 볼까 했지만 그럼 같은 금액이 엄청 많이 있을 텐데, 나중에 책마다 금액을 바꾸고 싶어 할 경우 책이 많아진다면 뭔가 엄청 많은 데이터를 업데이트 해야 될 수도 있어 보여서 일종의 책 종류 코드를 대신 넣고 가격 테이블은 책 종류 코드에 따라 부여되게 별도로 저장해 보려 한다. 그럼 RDB 의 기본 목표처럼 저장되는 데이터 크기도 아끼고, JOIN 쪽도 그다지 부담은 안 될 것 같다. 

책 코드에 따라서 대여기간 등도 조정 가능하도록 테이블로 관리할 수 있을 것 같다(이걸 프로그램 로직에서 관리할지 데이터베이스에 넣을지 고민을 할 수도 있겠지만, 프로그램을 새로 만드는 것보다는 데이터베이스의 값을 간단히 업데이트 하는 게 편할 듯 해 보인다. DBA 쪽에서 변경 시 작업해야 되는 번거로움은 생길지 모르겠지만 보통 이런 중요한 값이라면 관리되는 admin 페이지가 있어서 감사 로그와 함께 저장될 가능성이 높다).

회원 가입 시 받아야 할 정보도 있을텐데, 현재 시점에서 꼭 필요한 개인정보만 생각하면 이름, 고유정보를 체크하거나 연체 독촉 시 사용할 핸드폰 전화번호만 생각하면 될 것 같다. 직접 입력하는 회원 아이디도 굳이 이쪽에서 찾을 때 필요한 부분이니 필요가 없을 듯하다, 나머지 추가적인 정보 들은 차후 고민해 보자.

책을 빌려주게 되는 상황이 되면 대여에 관한 테이블이 하나 필요할 것 같다. 대여 번호가 고유키 일거고, 조회 시 중요한 필드는 회원 아이디가 될 것 같고, 들어가는 값들을 책의 일련번호, 현재 날짜가 들어가는 대여 시작일, 대여 종료 일은 책 종류에 따라 대여 일수를 정하려 하니(만화책 3일, 소설 5일 등) 아까의 책 가격 테이블을 확장 시켜서, 책 코드 별로 가격에 대여 기간도 같이 저장해 보자. 나중에 대여 기간을 연장 한다던지 하는 여러 기능이 추가될 수도 있고, 특정 시기에 대여 기간 정책을 바꿀 수 있기 때문에 시작 일과 종료 일은 명확히 하는 게 좋겠다. 또 해당 대역의 상태를 관리하기 위해서 하나의 상태 코드를 만들면 좋을 거 같다. 처음 대여 했을 때는 Rent 로 했다가 반납하게 되면 Return 상태가 되도록 하게 말이다. 음 연체료를 계산하거나 실제 반납 일이 다른 수 있으니 반납 일도 하나 넣는 것도 좋겠다. 

이렇게 생각하다보니 거래나 너무 많아지게 되면 나중에 테이블에서 이런 부분을 조회하는 부분이 부담이 될 것도 같지만 현재 프로그램의 특성 상 그렇게 많은 데이터가 쌓이진 않을 듯 하니, 반납이 완료된 히스토리 테이블을 따로 분리하는 건 차후에 고민하도록 해보자.

 



위와 같은 고민으로 AI 한테 부탁할 테이블 정보를 정리해 봤다. 아무래도 모두 우리말로 적다보면 테이블이나 컬럼 이름 같은 걸 너무 딱딱하게 적어서 맘에 안 드는 경우가 있어서, 지금 까지 배운 쿼리 지식을 이용해서 하나하나 최대한 정의를 해봤다. AI 가 알아서 잘 해주는 거 같은 Foreign 키 정의 같은 건 일단 그냥 빼놓자.

책 대여하는 프로그램을 sqlite 로 설계를 해보려고 해. 아래와 같은 테이블 들을 간단히 만들고 싶어.

1) 일단 책을 저장하는 테이블은 book 으로 하고, 책 일련번호(book_no), 책 제목(book_name), 책 타입(book_type - 나중에 가격이나 대여기간을 여기다 넣으려해) 컬럼을 만들어줘. 책이 대여나 파손, 또는 불가 상태가 되면 대여 가능 상태 여부를 알 수 있게 status 컬럼도 하나 만들어 줄래? 여기서 아마 고유 값은 book_no 만 있을 거 같네.

2) 책 종류에 대한 대여 정책 정보를 관리하는 book_policies 테이블을 만들고, 여기는 책 타입(book_type) 과 가격(price), 대여 기간(rental_period) 컬럼을 만들어줘. 아 연체시 하루 연체료(penalty_per_day) 도 넣어야 할 거 같네. 나중에 혹시 코드별 가격이나 대여기간 변경이 필요하면 이 테이블을 수정하려 해. 여기서는 book_type이 고유 값이 될거 같아.

3) 회원정보(customers) 테이블은 최소한의 개인정보만 저장하려해서 회원 고유 아이디(customer_id), 회원 이름(name), 한국 핸드폰 번호기준(phone), 회원 가입일(reg_date)만 넣으려해. 이름은 중복이 가능하니 핸드폰 번호(또는 이름+핸드폰 번호)가 아마 손님이 왔을때 검색하는 고유키가 될거 같네. 나머지는 기능이 확장되면 고려해 보려고. 여기서는 customer_id 와 phone이 중복이 있음 안 될거 같아.

4) 책 대여 테이블(book_rentals) 을 만들려 하는데, 대여 일련번호(rental_no), 회원 아이디(customer_id)가 주요 where 조건 키 일 거 같고(하지만 여러권을 책을 빌릴 수 있으니 회원 아이디는 중복은 있을 거야), 책 일련번호(book_no)와 나중에 혹시 대여기간 등이 변경 될수도 있으니 빌려줄 당시의 대여 시작일(rental_start_date)과 대여 종료일(rental_end_date)을 넣고 싶어. 또 대여, 반납, 취소 등의 상태를 관리하는 상태 코드(status)와 반납일(return_date)도 있음 좋겠어. 거래가 완료된 건만 타 테이블로 분리해 이관하는건 나중에 데이터가 많아짐 고민해 보려고. 여기서는 rental_no 이 중복 값이 없을 거 같아.

5) 조금 과한거 같긴 하지만 매출 등의 계산을 위해서 book_borrow_history 테이블을 만들려해 하다가 그냥 대여 테이블(book_rentals)에서 조회 할까도 싶은데 어떻게 생각해?

 




자 그럼 위의 내용을 기반으로 AI 가 만들어 준 테이블 정보를 보면서 우리가 의도한 대로 만들어 줬나 생각해 보자(이걸 직접 해봤던 사람들은 이런 게 얼마나 귀찮고 인형 눈 끼우는 거 같은 일인지 잘 알고 있을 것이다.

1) 결과적으로 히스토리 테이블은 나중에 규모가 커지면 만들라고 하고, 아래와 같이 4개의 테이블을 만들어 주고, 앞에서 얘기한 데이터가 많을 경우 검색을 빠르게 하기 위한 인덱스도 추천해 줬다. 



2) 책 정책 테이블은 책 타입을 고유 값("PRIMARY KEY") 으로 해서 중복을 막으면서 인덱스를 만들고, 가격이나 기간은 연체료는 꼭 입력 시 있도록 강제 하기 위해서 "NOT NULL" 코드가 들어갔다. SQLite 는 데이터 형태가 간단해서 글자는 TEXT, 숫자는 INTEGER 로 정의했다.

CREATE TABLE book_policies (
    book_type TEXT PRIMARY KEY,        -- 책 타입 (고유 값)
    price INTEGER NOT NULL,             -- 대여 가격
    rental_period INTEGER NOT NULL,     -- 대여 기간 (일 수)
    penalty_per_day INTEGER NOT NULL    -- 연체료 (하루당)
);




3) 책 정보 테이블은 책 일련 번호를 고유 값으로 하고, 자동("AUTOINCREMENT") 증가 되게 했다. book_type 은 외래키("FOREIGN KEY")로 해서 book_policies 에 들어 있지 않는 값이 못 들어 가도록 방지 했다. status 는 적당히 대화를 해석해서 네 개의 상태 값(가능, 대여 중, 훼손, 분실)이 들어가는 걸로 가정했는데, 뭐 그냥 실습이니 나쁘지 않아 보이니 넘어가자. 또한 status 는 빈 값이 들어올 경우 "AVAILABLE" 이 들어간다.

CREATE TABLE book (
    book_no INTEGER PRIMARY KEY AUTOINCREMENT, -- 책 일련번호
    book_name TEXT NOT NULL,                   -- 책 제목
    book_type TEXT,                            -- 책 타입 (외래키 후보)
    status TEXT DEFAULT 'AVAILABLE',           -- 상태 (AVAILABLE, RENTED, DAMAGED, LOST)
    FOREIGN KEY (book_type) REFERENCES book_policies(book_type)
);



여기서 하나 좀 애매한 부분이 있는데, status 와 book_type 이 "NOT NULL" 조건이 없다는 부분이다. 아마도 AI 생각을 추측해 보면 status 는 default 가 있어서 값을 안 넣어도 'AVAILABLE' 을 자동으로 넣어주어 괜찮고, book_type 은 외래키가 이상한 값을 막아 줄거라고 생각해서 뺀 거 같은데, 현실 적으로 보면 아래와 같은 쿼리에 대해서 실제 NULL 이 들어가는 이슈가 있을 수 있다(궁금하면 위의 테이블의 이름을 book_test 로 만들어서 직접 넣어보자).

INSERT INTO book (book_name, status) VALUES ('SQL 테스트', NULL);

<NULL 입력 테스트>

 

그래서 명시적으로 2개의 필드도 not null 이 들어가는 게 맞을 것 같고, 해당 부분을 다시 AI 한테 물어보니 그게 맞을 것 같다고 의견을 조정했다. 그럼 아래와 같이 조정할 수 있다. 대신 이 경우는 하나의 단점이 status 를 꼭 지정해 넣어줘야 한다는 부분이다. null 어떻게든 안 넣거나 막을 자신이 있으면 "NOT NULL"을 빼는 것도 하나의 선택지일 것도 같다.

CREATE TABLE book (
    book_no INTEGER PRIMARY KEY AUTOINCREMENT, -- 책 일련번호
    book_name TEXT NOT NULL,                   -- 책 제목
    book_type TEXT NOT NULL,                            -- 책 타입 (외래키 후보)
    status TEXT NOT NULL DEFAULT 'AVAILABLE',           -- 상태 (AVAILABLE, RENTED, DAMAGED, LOST)
    FOREIGN KEY (book_type) REFERENCES book_policies(book_type)
);





4) 회원 정보 테이블도 날짜에 NOT NULL 이 없어서 추가했고, 나중 코드를 보면 알겠지만 SQLite 는 날짜 형이 실제 없다고 하고, 쿼리 상에서는 날짜 같이 취급되지만 실제로는 TEXT 로 저장된다고 한다. 가벼운 효율성을 위해서 그랬다나 한다. 여기도 중복 안 되야 하는 값을 고유 값을 하고, 핸드폰의 경우는 고유 값은 아니지만(이걸 고유 값으로 잡으면 개인정보를 모든 다른 테이블의 키로 잡아야 해서 당연히 적절치 않아 보인다) 겹치는 값이 들어가면 안 되는 실제의 내부 고유 값 이여야 하므로, 겹치는 값이 못 들어가는 제한을 주기 위해 "UNIQUE" 를 넣었다. 

CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY AUTOINCREMENT, -- 회원 고유 아이디
    name TEXT NOT NULL,                            -- 이름
    phone TEXT UNIQUE NOT NULL,                    -- 핸드폰 번호 (중복 불가)
    reg_date DATE NOT NULL DEFAULT (DATE('now'))            -- 회원 가입일
);





5) 대여 테이블도 마찬가지로 status 에 "NOT NULL"을 추가했고, return_date 야 말로 빌려주자마자 반납 하는 경우는 당연히 없을 테니, 나중에 필요한 순간에 날짜가 들어가게 NULL 허용이 되게("NOT NULL" 이 없음 허용이다) 했다. 마찬가지로 똑똑하게 customer_id, book_no 는 회원 테이블과 책 정보 테이블에 없는 값은 안 들어가도록 외래키를 걸어줬다.

CREATE TABLE book_rentals (
    rental_no INTEGER PRIMARY KEY AUTOINCREMENT, -- 대여 일련번호
    customer_id INTEGER NOT NULL,                -- 회원 아이디
    book_no INTEGER NOT NULL,                    -- 책 일련번호
    rental_start_date DATE NOT NULL,             -- 대여 시작일
    rental_end_date DATE NOT NULL,               -- 대여 예정일
    return_date DATE,                            -- 실제 반납일
    status TEXT  NOT NULL DEFAULT 'RENTED',                -- 상태 (RENTED, RETURNED, CANCELLED)
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
    FOREIGN KEY (book_no) REFERENCES book(book_no)
);





6) 이 밖에 status 값도 AVAILABLE, RENTED, DAMAGED, LOST 네 개로 제한(CHECK (status IN ('AVAILABLE', 'RENTED', ...)) 하자는 의견도 있었는데, 이건 안정성 보다는 상태가 새로 생길 때마다. 제약 조건을 업데이트 해야 하는 부분이 걸려서 그대로 두기로 한다. 

7) 회원이 방문했을 때 찾는 키가 이름하고 폰 번호일 것 같아서 아래와 같이 이름하고 전화번호 컬럼에 인덱스를 거는 것도 제안 했는데, 이것도 결국은 실제에서는 전화번호 뒷자리 4자리로 찾는 게 개인정보 보호 상 현실적일 것 같아서, 나중에 전화번호 뒷자리 컬럼을 하나 더 만드는 게 날 것 같긴 해서 실습에서는 넘어간다.

CREATE INDEX idx_customer_search ON customers(name, phone);

여하튼 AI 쪽에서는 범용적인 관점에서 내가 빼놓은 걸을 잘 체크해 주는 편이므로, 해당 부분을 잘 이용해서 비어 있는 부분을 잘 채우고 자신과 AI 둘 다 괜찮다고 생각하는 협의 점을 찾아보는 게 어떨까 싶다.

 

테이블 생성 후 데이터베이스 구조를 보면 아래와 같다. 중간에 null 테스트를 안 해봤다면 book_test 테이블은 없을 거다

<책 대여 프로그램 - 테이블 생성 후>








그 다음에 테이블이 잘 만들어 진 것을 증명하고, 차후 프로그램에서 이런 저런 쿼리를 날렸을 때 잘 되려는 지 검증을 위해서 책을 등록 하고, 고객을 등록하고, 대여하고, 반납하는 간단한 시나리오에 대한 쿼리를 만들어 보자. 위의 테이블 들을 설계하면서 머리 속에 대충 테이블들이 이제는 그려졌을 테니, 그걸 기반으로 아래와 시나리오를 만들어 봤다.

 시나리오를 한번 설명해 줄께. 관련된 쿼리를 만들어 줄래? not null 인 영역에 default 값이 있다고 빼먹고 그러진 말아줘. 그리고 상태 값이나 타입 값은 소문자로 부탁해
1) 책을 만화책(comic)과 소설(novel) 로 나눔. 각각 대여료는 500원, 1000원, 대여기간은 3일, 5일, 연체료는 하루에 100원, 200원 이야.
2) 책이 2권 들어왔는데 원피스(만화)랑, 채식주의자(소설)이야. 이 책들을 대여가 가능하게 등록 해야되.
3) 회원의 두 명 가입했는데 "홍길동"이 "010-1111-1111" 로 가입 했고, "김고객"이 "010-2222-2222" 로 가입 했어.
4) 이후 "홍길동"이 찾아와서 "이름, 전화번호 뒷자리로" 회원을 확인하고 원피스를 빌려갔어
5) 근데 10일 후에 책을 반납 했네. 7주일치 연체료를 계산해야 되.
6) 연체료를 받은 후에는 책을 반납해서 완료하려고.




AI가 만들어 준 각각의 코드를 봐보자.

1) 책을 만화책(comic)과 소설(novel) 로 나눔. 각각 대여료는 500원, 1000원, 대여기간은 3일, 5일, 연체료는 하루에 100원, 200원 이야.
얘기한 대로 book_policies 테이블에 대여료, 기간, 연체료 기준으로 입력해 준다.

INSERT INTO book_policies (book_type, price, rental_period, penalty_per_day)
VALUES 
('comic', 500, 3, 100),
('novel', 1000, 5, 200);


넣은 데이터를 조회해 보자
select * from book_policies

 

<책 대여 프로그램 - 책 정책>




2) 책이 2권 들어왔는데 원피스(만화)랑, 채식주의자(소설)이야. 이 책들을 대여가 가능하게 등록 해야 되.

관련 책을 넣으면서 상태를 기본 상태인 'available' 로 만들어 준다.

INSERT INTO book (book_name, book_type, status)
VALUES 
('원피스', 'comic', 'available'),
('채식주의자', 'novel', 'available');


넣은 데이터를 조회해 보자.
select * from book

<책 대여 프로그램 - 책 정보>

 



3) 회원 정보를 넣는다. 가입 일은 현재 날짜로 넣는다.

INSERT INTO customers (name, phone, reg_date)
VALUES 
('홍길동', '010-1111-1111', DATE('now')),
('김고객', '010-2222-2222', DATE('now'));



넣은 데이터를 조회해 보자.
select * from customers

<책 대여 프로그램 - 회원 정보>




4-1) 사실 원래의 프로그램이라면 WHERE 조건에 좀더 간단해 질 수도 있고(이미 로그인 할때 customer_id를 가져와서 그걸 계속 사용한다거나), select 를 통해서 정보를 조회헤 온후, 해당 정보를 기반으로 insert 할 수도 있을 것 같다. 여기서는 하나의 쿼리로 나타내기 위해서 아래와 같이 insert 와 select 가 결합된 형태로 나타났다. "vaulus" 키워드가 사라진게 좀 특이해 보인다. 날짜는 SQLite 에서 지원하는 방식으로 현재(DATE('now'))와 현재에 대여기간을 더 한 형태(DATE('now', '+' || p.rental_period || ' days'))로 보여준다. 참고로 이런 날짜를 계산하는 방식은 데이터베이스마다 보통 조금씩 다르다.

INSERT INTO book_rentals (customer_id, book_no, rental_start_date, rental_end_date, status)
SELECT 
    c.customer_id, 
    b.book_no, 
    DATE('now'), 
    DATE('now', '+' || p.rental_period || ' days'),
    'rented'
FROM customers c
JOIN book b ON b.book_name = '원피스'
JOIN book_policies p ON b.book_type = p.book_type
WHERE c.name = '홍길동' AND c.phone LIKE '%1111'
LIMIT 1;


넣은 데이터를 조회해 보자.
select * from book_rentals

<책 대여 프로그램 - 대여 정보>


4-2) 추가로 도서 상태도 대여로 바꾸어줘 보자.

UPDATE book 
SET status = 'rented' 
WHERE book_name = '원피스';


업데이트 된 데이터를 조회해 보자.
select * from book WHERE book_name = '원피스';

<책 대여 프로그램 - 대여 상태 변경>




5) 연체료 계산을 하다보니 테스트로 만든 쿼리라고 강제로 빌린 기간에 10일을 더 해서(DATE(r.rental_start_date, '+10 days')), 반납일로 정해 봤다. 그러다 보니 조금 헷갈리긴 할 듯 하다. 지난 기간을 체크하기 위해서 날짜를 JULIANDAY 라는 함수로 변환 후 다시 CAST 하는데, JULIANDAY 는 "기원전 특정일" 로 부터 흐른 날이라고 하고, CAST AS INTEGER는 원래 해당 값이 실수로 나올 수 있는데, 그걸 소수점을 버리는 거다. 여러 현실 적인 부분에서 연체로 계산하는 날짜 계산 방식이 바뀔 수 있는데(뭐 예를 들면 밤 11시에 빌리나 오전 11시에 빌리나 다음날 반납해야 되는 불공평함을 어떻게 개선해 본 다든지), 그 경우는 현재 날짜만 저장하는 DATE 를 DATETIME 으로 바꾸거나 하는 일이 생길 수도 있다. 여하튼 모든 건 목적을 위해서 설계하기 나름이라고 보자.

밑의 쿼리는 WHERE 조건에서는 약간 단정적으로 "홍길동"이라는 사람이 "원피스"를 빌려갔다고 가정하는데, 사실 현실에서는 이름은 고유 값이 아니기 때문에 특정 customer_id 의 현재 미 반납 목록 이라든지 그런 걸 볼 수 있으므로, WHERE 조건은 얼마든지 바뀔 수는 있어 보인다. 오히려 c.name은 고유 값이 아니므로 저렇게 사용하기가 좀 위험해 보이긴 하는데, 스크립트와 결과에 맞춰 쿼리를 만들어 주다 보니 그렇지 않을까 싶긴 한다.

SELECT 
    c.name AS 회원명,
    b.book_name AS 도서명,
    r.rental_end_date AS 반납예정일,
    DATE(r.rental_start_date, '+10 days') AS 실제반납일,
    CAST(JULIANDAY(DATE(r.rental_start_date, '+10 days')) - JULIANDAY(r.rental_end_date) AS INTEGER) AS 연체일수,
    (CAST(JULIANDAY(DATE(r.rental_start_date, '+10 days')) - JULIANDAY(r.rental_end_date) AS INTEGER) * p.penalty_per_day) AS 총연체료
FROM book_rentals r
JOIN customers c ON r.customer_id = c.customer_id
JOIN book b ON r.book_no = b.book_no
JOIN book_policies p ON b.book_type = p.book_type
WHERE c.name = '홍길동' AND b.book_name = '원피스' AND r.status = 'rented';

 

<책 대여 프로그램 - 10일 후 연체료 계산>



6-1) 마지막으로 대여한 사람이 10일 후에 반납했다고 보자. 밑의 조건은 관점에 따라 약간 과할 수도 있는데, 특정 사용자 id 와 책을 상태를 굳이 맞추는 것 보다, 스캐너로 스캔한 책 번호와 status 만 있어도 될 것 같 긴 한데, 그래도 확실히 하려면 빌려간 id 도 보는 게 날테니 일단 그대로 두자.

UPDATE book_rentals 
SET return_date = DATE(rental_start_date, '+10 days'), 
    status = 'returned'
WHERE customer_id = (SELECT customer_id FROM customers WHERE name = '홍길동' AND phone LIKE '%1111')
  AND book_no = (SELECT book_no FROM book WHERE book_name = '원피스')
  AND status = 'rented';



6-2) 책도 정상 상태로 돌려주자.

UPDATE book 
SET status = 'available' 
WHERE book_name = '원피스';



마찬가지로 앞에서 수행했던 쿼리들을 이용해서 book_rentals 와 book 테이블이 업데이트 된 부분을 보자.

<책 대여 프로그램 - 반납 시 대여 정보 업데이트>

 

<책 대여 프로그램 - 반납 시 책 정보 업데이트>








다음엔 여기까지 온 김에 프로그램에서 쿼리를 호출하는 모습도 한 번 봐보자.

밑의 코드는 AI 쪽에서 추천해 준 대로 streamlit 이라는 파이썬 라이브러리를 가지고 윈도우에 만들어 놓은 SQLite 파일을 기준으로 만들어준 코드이다. 

import streamlit as st
import sqlite3
import pandas as pd
from datetime import datetime

# 1. 데이터베이스 연결 함수
def get_data():
    conn = sqlite3.connect('c:\Sql\DB.Browser.for.SQLite-v3.13.1-win64\mytest.db')

    # 오늘 날짜를 기준으로 대여된 현황 조회 쿼리
    query = """
    SELECT 
        r.rental_no AS '번호',
        c.name AS '회원명',
        b.book_name AS '도서명',
        b.book_type AS '타입',
        p.price AS '대여료',
        r.rental_end_date AS '반납예정일',
        r.status AS '상태'
    FROM book_rentals r
    JOIN customers c ON r.customer_id = c.customer_id
    JOIN book b ON r.book_no = b.book_no
    JOIN book_policies p ON b.book_type = p.book_type
    WHERE r.rental_start_date = DATE('now')
    """
    df = pd.read_sql(query, conn)
    conn.close()
    return df

# 2. 스트림릿 UI 구성
st.set_page_config(page_title="오늘의 대여 현황", layout="wide")

st.title("📚 오늘 도서 대여 관리 현황")
st.write(f"조회 일시: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

# 데이터 불러오기
df = get_data()

if not df.empty:
    # 요약 정보 (메트릭)
    col1, col2 = st.columns(2)
    col1.metric("오늘 총 대여 건수", f"{len(df)}건")
    col2.metric("오늘 총 매출", f"{df['대여료'].sum():,}원")

    st.divider()

    # 데이터 표 출력
    st.subheader("📋 상세 대여 리스트")
    st.dataframe(df, use_container_width=True, hide_index=True)
else:
    st.info("오늘 대여된 내역이 없습니다.")

# 새로고침 버튼
if st.button('데이터 새로고침'):
    st.rerun()




여기 서는 과정은 설명 하진 않고, 간단한 실행 모습과 결과만 보겠다. 해당 내용을 파이썬 파일(rental_book.py)로 저장 후, CMD 창에서 실행한 결과 이다. 파이썬을 조금 안다면 파이썬을 설치하고, 라이브러리를 설치하고, 파일을 실행하면 바로 된다. 일단 한 가지 주의할 점은 앞에서 얘기했지만 SQLite 는 로컬 디비 이기 때문에 지금까지 한 작업을 저장을 해야 이후 외부에서 해당 데이터에 실제 접근이 가능하다. DB Browser for SQLite 에서 "파일 > 변경사항 저장하기"를 꼭 실행 해주자. 

c:\Python\Code\linkedin>streamlit run rental_book.py

실행 하면 아래와 같이 깔끔한 페이지가 나오게 되는 데, 이건 파이썬 프로그램이 코드 중간에 있는 Sqlite 파일의 위치(결국 데이터베이스 위치)와 query 변수 안에 넣은 쿼리를 가지고 Sqlite 에 조회를 해서 결과를 얻은 후 해당 결과를 라이브리리에 잘 전달을 해서 결과를 보여준다(여기에 사실 테이블 형태의 데이터 정리를 도와주는 pandas 라는 라이브러리도 있는데 뭐 지금은 코드를 배우는 시간은 아니니 그려러니 하자). 

<파이썬 프로그램으로 SQLite 호출 하기>

 


실제 저 코드 안의 query 라는 변수 안에 있는 SQL 쿼리 문장(이제는 우리는 잘 구분할 수 있다)을 긁어다 DB Browser에서 조회하면 아래와 같이 동일한 데이터를 볼 수가 있다. 다만 프로그램에서는 저 모양 그대로가 아닌 여러 프로그램 로직으로 가공한 그래프를 포함한 다양한 모습으로 데이터들의 표현이 가능할 것이다.

<쿼리만 잘라 실행해 보기>

 

 

 

 



마지막으로 CMD 에서 실행하는 예제만 보고 마무리를 해보자. 이것도 실습을 할 건 아니라서 해보고 싶은 사람을 위해서 간단히 설명하자면, https://www.sqlite.org/download.html 사이트에서 sqlite-tools-win-x64-3510200.zip를 다운로드해 압축을 풀고 압축을 푼 디렉토리로 가서 CMD 창에서 아래와 같이 실행하고, "sqlite>" 로 입력 프롬프트가 바뀌면 이후 밑의 쿼리를 넣어본다.

c:\Sql\sqlite-tools-win-x64-3510200>sqlite3 c:\Sql\DB.Browser.for.SQLite-v3.13.1-win64\mytest.db

 SELECT 
        r.rental_no AS '번호',
        c.name AS '회원명',
        b.book_name AS '도서명',
        b.book_type AS '타입',
        p.price AS '대여료',
        r.rental_end_date AS '반납예정일',
        r.status AS '상태'
    FROM book_rentals r
    JOIN customers c ON r.customer_id = c.customer_id
    JOIN book b ON r.book_no = b.book_no
    JOIN book_policies p ON b.book_type = p.book_type
    WHERE r.rental_start_date = DATE('now');


 
이건 실행된 그림으로 대신 한다.

<CLI 를 통한 실행>






왠지 글이 길어질 거 같아서 손을 못 대고 있었는데 역시 꽤 길어지긴 했고(이 정도 글이면 링크드인 편집기가 좀 많이 버벅댄다;), 다행히 억지로 마무리는 된 듯하다. 생각보다 조회 쿼리가 많지 않아서 좀 따라가기가 힘들 것 같긴 한데 그대로 전체적인 사이클을 한번 보여주는 게 좋을 것 같아서 CRUD 를 골고루 섞어 봤다(지금 보니 D 는 중간에서 테스트 하다 개인적으로 지우기만 했긴 했다). 사람들이 데이터베이스에 이렇게 쿼리를 날리기는 하지만 실제에서는 그보다 몇 백만 배로 프로그램들(여러가지 API 나 어플리케이션)에서의 호출이 일어나는 게 더 많다고 보면 된다. 어찌 보면 뒤에서 여러 종류의 데이터베이스를 또 살펴 보겠지만 IT의 심장 같은 느낌을 가지게 된다. 

RDB에서 보통 프로그램에서는 성능 상의 이유로 저 WHERE 뒤의 변수 들을 고정 시켜 놓고, 해당 값들을 프로그램 내에서 가공해서 넣게 된다. 예를 들면 로그인 할 때 아이디, 해싱된 패스워드를 기준으로 유효한 cusommer_id를 메모리에 잘 저장한 후, 해당 값을 인자로 하는 여러 종류의 Query 를 호출하면서 데이터베이스로부터 가져와 프로그램에서 이용하게 된다. 조금 억지로 확장하자면 데이터베이스를 사용하지 않는 프로그램들도 그 나름대로의 데이터를 저장하는 구조를 가지고 해당 데이터를 처리하고 교환하면서 움직이게 된다. 그래서 데이터베이스를 이해하는 것이 어찌 보면 프로그램을 쉽게 이해하는 길이고, 또 프로그램 세상을 이해하는 것이 요즘 한참 얘기 되는 AI 가 만드는 자동화된 agent 의 세상이나 자동화된 프로그램을 쉽게 이해하는 한 발판이 되어주지 않을까 생각한다. 개인적으로는 데이터베이스가 데이터의 심장을 목표로 한다면 AI 는 로직의 심장을 목표로 하는 거 같다고 보기 때문이다. 아직 손발을 이루는 기술은 그대로가 아닌가도 싶다.

여하튼 여기에서 쿼리 자체를 설명하는 부분을 마치고, 다음 번에는 다른 여러 서버 형식의 RDB 가 현재의 SQlite 와 어떤 다른 포지션을 가지고 있는지 보려고 한다. 그 후에는 NoSQL을 함 보려하고, 그 담에 아직은 조금 애매한 기타 디비들, 그 담엔 보안에서의 데이터베이스를 얘기 잠깐하고 마무리를 향해 달려갈까 생각은 하고 있다.

<Fin>

posted by 자유로운설탕
2026. 3. 2. 18:14 프로그래밍

이번 얘기를 하기 전에 앞에서 얘기한 내용들을 다시 한번 정리해 보자. 

우선 첫 글에서 데이터베이스와 테이블에 대한 얘기를 했었다. 데이터베이스(Database)는 바깥 세상의 데이터 들을 파일 형태로 잘 저장하여 이용하기 위해서 만든 것이고, 테이블(Table)은 엑셀 같이 네모난 격자 형태로 데이터를 추상화 해 저장한 것이다(그렇다고 실제 데이터가 네모나진 않을 것이다). 데이터의 의미를 나타내는 세로 줄인 컬럼(Column)이라는 개념이 있고, 가로 줄은 로(Row)라고 해서 데이터의 행 수 또는 개 수를 나타낸다. 테이블의 제일 큰 장점은 사람에게 친숙한 네모난 칸 들로 보이게 만들어져 꽤 직관적이고 가시적이라는 것이다. 요즘의 데이터 세상에서 볼 수 있는 눈에 보이지도 않고, 수학적으로 상상해야 되는 다 차원 데이터 구조 들에 비해서는 특히 말이다.

두 번째 글에서 방 안의 특정 서랍 안에서 야구 공을 찾는 그림을 통해서, 데이터베이스에서 하나의 테이블 안의 데이터 행의 컬럼 하나를 찾아오는 예제를 보여주고, 영어의 문법 순서를 기반으로 Select, Insert, Delete 그리고 테이블을 만드는 Create 문까지 체크해봤다(지금 뒤돌아 보니 데이터를 변경하는 Update 문을 빼놓긴 했지만 Update 문도 "UPDATE 특정 컬럼을 어떻게 WHERE 어디에서"의 형태로 delete 와 거의 비슷하다고 보면 된다). 그 과정에서 테이블을 만들 때 쓰는 DEFAULT, NOT NULL 등의 키워드 들을 잠시 언급하며 그런 키워드들이 테이블을 사용하는 다양한 외부 환경(프로그램)에 대해서 테이블 안의 데이터들의 정합성을 잘 지켜주는 편리한 도구라는 얘기를 했었다.

세 번째 글에서는 잠시 하나의 테이블을 벗어나서 (두 개의) 테이블 간의 관계를 나타내는 썸과 비슷한 JOIN 이라는 키워드와 약간의 변형인 짝사랑 같은 LEFT JOIN 에 대해서 얘기하면서 텅 비어있는 NULL 이라는 키워드에 대해서도 지나가며 언급 했다. 그 담엔 UNION을 통해서 서로 같거나 이질적인 테이블들의 컬럼이 서로 힘을 합쳐 하나의 테이블 같이 동작하는 부분을 얘기하고, 이 부분에서 SQL 쿼리의 결과 또한 하나의 가상의 테이블 형태여서 그걸 자유롭게 사용할 수 있다는 것도 슬쩍 얘기한 것 같다. 그리고 하나의 테이블 안의 사건이긴 하지만 데이터 들 간의 관계를 그룹 짓는 GROUP BY 를 얘기하며, 단체 안에서의 개인의 사라짐과, 그 대신 생겨나는 수치적 통계들에 대해서 얘기 한 듯 하다.



 

테이블들이 혼자서 또는 여럿이서 움직이는 부분을 보여줬고, 그 결과 또한 가상이나 실제의 테이블로 귀결 된다는 것을 보여줬으므로, 이제 가져온 데이터를 어떻게 표현하고, 또 데이터를 가져오는 과정을 어떻게 좀 더 정교하게 하는 지에 대한 문법적인 얘기만 남은 것 같다. 

앞에서 설명한 부분과 연결한다면 "SELECT *** FROM *** WHERE ***" 에서 SELECT 와 FROM 사이에 어떤 일이 생기고, WHERE 뒤에 어떤 일이 생길 수 있느냐 에 대한 얘기이다. 사실 이 부분은 우리가 업무에서 많이 사용하는 엑셀을 상상해 보면 쉽게 알 수 있는 부분이기도 하다. 우리가 엑셀에 특히 구조화된 표 모양의 데이터를 넣은 뒤 주로 하는 일이 바로 그 데이터 들을 조합해 새로운 데이터를 만들어 내거나, 중복 데이터를 삭제 하거나, 합하거나, 조건 문을 걸어 조건에 따른 분류를 하거나 같은 일이다. 또한 데이터를 정렬하거나, 특정 조건이나 날짜의 데이터만 걸러내거나 특정 샘플 만을 가져오거나 하려 한다. 

이와 비슷한 일을 우리는 SQL 문장을 통해서 할 수 있다고 보면 된다. 다만 엑셀에서 표 형태의 데이터를 조작할 때의 제약들처럼 같은 형태의 데이터를 가지고 정리한다고 볼 수 있기 때문에, 아마 처음 사용하다 보면 보기에는 잘 돌아갈 거 같은 쿼리들이 에러가 나기도 할 것이다. 해당 부분은 요즘 같이 AI 로 쿼리를 만들어 사용하다 보면 결코 만나지 못할 부분일 수도 있겠지만, 앞에 얘기한 관계들을 머리 속에 개념화 시키는데는 좋은 경험들이므로 한번쯤은 만나 고민을 해봤음 하는 바램도 있긴하다. AI는 일종의 자동화의 산물이라고 생각하는데 모든 자동화는 수동화의 실패 경험이 기본으로 배경으로 있을 때 가장 세련되게 구현될 수 있다고 보기 때문이다. 


 



[SELCET 와 FROM 사이의 요소들]

앞에서 일반적으로 모든 SELECT 쿼리는 결국은 테이블 모양의 데이터가 반환 된다고 했다. 해당 부분을 상상해 보면 반환 되는 것은 하나의 셀(Cell)일 수도 있고, 하나의 줄(Row)일 수도 있고, 하나의 열(Column)일 수도 있고, 네모난 상자일 수도 있다(결국은 네모난 상자에 모두 속하긴 한다). 보통 여기서 사용하는 연산자 들은 열을 기준으로 동작을 하게 된다. 

우선 결과로 보여주는 열에 대해서 SUM(합), COUNT(행 수), AVG(평균) 같은 부분을 결과로 낼 수 있다. 일반적으로는 이 함수를 사용하려면, 앞에서 설명한 GROUP BY 등으로 데이터를 묶어 사용하거나 테이블 전체에 대해서만 기본적으로 사용이 가능하다고 볼 수 있다. 그래서 만약 테이블에 직원 이름, 연봉이 있고, 결과로 직원 이름, 연봉, 회사 전체 연봉을 표시하고 싶었다면 아래와 같이 조금 비 효율 적인 서브 쿼리가 필요했었다. 

여기서부터 드물게 아래 사이트에서 쿼리 10개까지는 SQLITE 기준으로 데이터 저장부터 실습을 할 수 있음을 발견하여 사이트도 구경할 겸 현재 쿼리만 한번 실습을 해보자. 무료 사용의 경우 3시간 후나 브라우저를 종료했다 다시 열면 데이터가 모두 사라지는 듯 하니 참고하자. 기본적으로 서브스크립션 대여를 목적으로 한 사이트 같아 너무 쿼리를 자주 날려도 실례일 듯 하고, 또한 아마 해당 제한으로는 좀 더 연속적인 쿼리 과정에 대한 실습은 못할 듯 하기도 하고, 로컬 디비이긴 하지만 하나 설치해 보는 경험도 나쁘진 않을 듯 해서, 다음 시간에는 SQLITE 를 PC에 함 설치해 사용해 보도록 해보자.

https://sqliteonline.com/

<SQLITEONLINE 사이트>

 


화면에서 위쪽에 쿼리를 넣고 "Run" 버튼을 실행하면, 왼 쪽에 생성한 테이블 이름이 나오고, 아래 쪽에서는 조회의 경우에는 결과가 나오게 된다(처음엔 아래 부분을 한번 클릭해 가이드를 지워야 하는 듯도 싶다).

우선 테이블 생성 쿼리를 돌려서 테이블이 생성 됬음을 확인 하고,

CREATE TABLE employees (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    salary INTEGER NOT NULL
);




이후 데이터를 넣어보자, 앞의 글에서 살짝 언급 했듯이 id 라는 값은 자동 증가 하는 값이 들어가기 때문에 입력 시 생략을 해도 에러가 나지 않으면서도 일련번호가 생겨 관리하기 좋아진다.

INSERT INTO employees (name, salary) VALUES 
('박지민', 3500),
('이영희', 5000),
('홍길동', 4000);





아래가 바로 전통적인 해당 테이블을 이용해, "직원 이름, 연봉, 직원 전체 연봉" 을 보여주는 쿼리이다. 쿼리를 보면 컬럼 안에 서브 쿼리 형태의 쿼리가 하나 통째로 들어가서 전체 연봉을 sum 을 해서 컬럼 자리를 차지한다(앞에서 얘기했지만 모든 결과가 결국 테이블 형태기 때문에 이런 유연한 끼워 넣음이 가능한 듯 싶다)

SELECT 
    e1.name, 
    e1.salary,
    (SELECT SUM(e2.salary) 
     FROM employees e2) AS dept_total
FROM employees e1;

 

<SQL 쿼리 실행 결과>



그런데 위의 방식은 데이터베이스 최적화 엔진에 따라서 모든 행에서 반복으로 SUM 에 관련된 서브 쿼리가 실행될 수 있었기 때문에, 그래서 AI 가 하는 얘기로는 예전에는 아래와 같이 한번만 조회가 일어나게 되는 좀 복잡해 보이는 방식을 추천했다고 하는데, 현재는 최신의 컴파일러들이 알아서 비 효율 적인 코드를 실행 시 최적화 하듯이 위와 같이 작성해도 아래와 같이 실행해 줄 수도 있을 듯 싶다. 이런 게 보통 데이터베이스에서 얘기하는 쿼리 튜닝 요소이고 말이다.

SELECT 
    e1.name, 
    e1.salary, 
    total.dept_total
FROM employees e1
CROSS JOIN (
    SELECT SUM(salary) AS dept_total FROM employees
) total;




앞의 부분을 전통적인 이라고 얘기했던 이유는 이 방법을 안 쓰는 뒤의 새로운 문법들은 SQLITE 같은 경우는 2014~2018년에, 상용인 Oracle 이나 MSSQL 은 2000년 초에 생겼다고 한다. 또한 신 기능들은 아마 초기의 성능 개선이나 여러 요소 때문에 DBA 팀에서 사내 쿼리 표준에 보수적으로 포함 안 하는 경우가 많았을 터이기 때문에 아마도 2010년 중 후반에나 퍼졌을 것 같고, 그 후에 오픈 소스 디비들이 그 편리함들을 차용해 도입했을 것 같다. AI 에게 의견을 물어보면 하드웨어의 발전과 용량 확대 또한 많은 기여를 했다는 데, 하나의 서버가 모든 걸 커버해야 하는 RDB 의 경우는 특히 더 많은 영향을 받지 않았을까 싶다. 이건 뭐 주위의 좀 연식이 된 DBA분들이 있으면 함 여담으로 물어봐도 될 듯 하다.

그 다음에 나온 두 가지 방법이 OVER 와 공식적으로 CTE(Common Table Expression, 공통 테이블 식)이라고 얘기하는 WITH 인데, 먼저 OVER 를 보면 앞에서 설명한 GROUP BY 역할을 하게 되며 이런 부분에 특화가 되었다. 저 OVER 는 다른 요즘 함수인 RANK 나 LAG 등과 결합하면 그룹핑이나 정렬 함수가 들어가게 되는 거 같다.

SELECT 
    name, 
    salary,
    SUM(salary) OVER () AS dept_total
FROM employees;




마지막으로 마치 프로그램에서 변수를 저장하듯 테이블을 잠시 저장해 쓰는 WITH 라는 문구를 쓸 수 있다. 파이썬이라면 pandas 테이블이 하나 있는 떠 있는 듯 싶다고 표현하고 싶다. 

WITH GrandSum AS (
    SELECT SUM(salary) as total FROM employees
)
SELECT 
    e.name, 
    e.salary, 
    g.total as grand_total
FROM employees e 
CROSS JOIN GrandSum g;




아마 레거시 SQL 만 주로 보다 보면 위의 쿼리들이 조금 신기해 보일 수도 있을 것 같긴한데, 데이터 추출 하는 쪽 같은 데서는 자주 볼 수 있는 것 같기도 하고, 기존 문법의 비효율성과 성능을 개선하기 위해 만든 기능 들이고 성능 적으로도 충분히 안정화가 된 것 같으니 DBA쪽과 의논해 유용하게 써도 무방하지 않을까 싶다. 다만 해당 사용이 결국 뒤에 나오는 테이블의 인덱스 같은 부분들과도 연관 되니 그냥 내가 쓰고 싶다고 언제든 쓸 수 있는 부분은 아닌 듯은 싶다.

일단 하나의 자세한 예제를 보았으니 나머지는 후루룩 넘어가 보려고 한다. 위의 SUM 과 비슷하게 행의 수를 세는 COUNT와 평균을 계산하는 AVG 가 있으나 이것은 결국 데이터 군을 어떻게 해석하려 하느냐의 차이이니 굳이 추가적인 예제는 필요 없을 듯 싶다.





다음으로는 CASE 문이 있는데 이건 프로그래밍 에서도 많이 보이는 형태이기도 하고, 사실 나중에 프로시저라는 반복이나 여러가지 조건 처리를 위해 여러가지 쿼리들이 조합된 방식으로 만들려고 하다 보면 결국 스크립트 형태의 프로그램이 되어버리긴 한다(다만 우리가 사용하는 SQLITE 는 경량 화에 특화되고 그런 복잡한 로직은 프로그램을 통해 해결하라는 철학을 가지고 있기 때문에 프로시저 문법은 지원 안 한다고 한다). 

문법을 보면 CASE WHEN ELSE END 가 주인공 이며 결과는 결국 하나의 값이 된다. AI 한테 영어로 함 문장을 만들어 달라고 해봤다.

CASE 
    WHEN life gives you lemons 
    THEN make lemonade
    ELSE just smile and stay positive
END




예를 들어 앞의 예에서 전체 직원 월급의 평균 보다 같거나 높으면 "이상", 낮으면 이하라고 표기하고 싶다면, 앞의 현대적인 문법인 OVER 를 배웠으니 그걸 이용해서 만들면 아래와 같다. 

SELECT 
    name, 
    salary,
    CASE 
        WHEN salary >= AVG(salary) OVER() THEN '이상'
        ELSE '이하'
    END AS salary_status
FROM employees;





또한 앞에서 얘기한 짝사랑의 관점에서 의미가 없이 비어있는 NULL 값을 치환해 주는 함수들도 있는데, IFNULL 이나 현재에 많이 쓰인다는 COALESCE 라는 키워드가 있다고 한다. 저런 예전 키워드 들은 약간 상황에 따라서 모양이 다른 경우가 있다. 예를 들면 IFNULL 이 MSSQL 에서 ISNULL 이고, 오라클에서는 NVL 이라고 한다. COALESCE 는 공통으로 사용되며 인자가 여러개 들어가 A 가 아니면 B , 아니면 C 로,... 해서 대체제를 다수 찾아줄 수 있어 더 유연성이 좋다고 한다.

직원들의 평균 연봉을 구하는데 연봉 금액을 비워 놓은 직원이 있어서(Null 존재) 해당 경우는 어림 잡아 4000으로 잡아서 계산하는 쿼리를 만들어 보자. 만약 저런 예외 처리가 없다면 Null 은 데이터베이스의 종류에 따라 맘대로 0으로 처리되거나 집계에서 빠져버릴 수 있다고 한다. 뭐랄까 이런 함수의 용도는 얘를 무시하지 말고 제도 권에 받아들이자의 의미라고 봐도 된다.

SELECT AVG(COALESCE(salary, 4000)) AS adjusted_average
FROM employees;






다른 테이블에 거리 주소와 상세 주소가 나눠져 있다고 보자. 2개를 가져와서 프로그램에서 합쳐도 될 수 있겠지만, 결과 자체에서 그렇게 보고 싶다면 SQL 종류에 따라 특정한 연산자(||, +)나 CONCAT_WS 같은 문자열을 합치는 키워드 들이 있다. AI 가 만들어준 예제에서는 친절하게도 혹시나 상세 주소가 비어서 에러 날 경우를 대비해서 앞에서 배운 COALESCE 함수로 NULL 일 경우를 대비해 주어서 그대로 가져왔다.

SELECT 
    name,
    address_road || ' ' || COALESCE(address_detail, '') AS full_address
FROM employees;





다음도 약간 프로그래밍 적인 요소인데(뭐 근데 엑셀도 마찬가지지만 함수를 사용하기 시작하면 스크립트 기반의 프로그래밍 영역에 들어갔다고 보면 될 것 같다) 데이터의 형을 바꿔주거나 날짜 포맷 등을 바꿔줄 수 있다. 예를 들어 직원의 "년월일"로 들어간 입사일 데이터를 월 별로 GROUP BY 해 "년월"별 입사한 직원 수 통계치를 뽑기 위해서, 입사일 데이터를 일단 "년월"별로 변환 후 GROUP BY 하여 수를 세는 예제를 만들면 아래와 같다. 실제 입사일 데이터는 일 또는 시간까지 들어간 데이터일 수도 있지만 잘 변환해 원하는 결과를 가져온다.

SELECT 
    strftime('%Y-%m', hire_date) AS join_month,
    COUNT(*) AS employee_count
FROM employees
GROUP BY join_month







[WHERE 뒤의 요소들]

생각보다 앞의 얘기가 길어진 듯 하지만 다음엔 WHERE 뒤이다.

사실 위의 WITH 나 OVER 가 나온 후에 이 WHERE 뒤 쪽이 SELECT 와 FROM 사이에서도 자주 보이게 됨으로서 조금 서로 간의 경계가 깨어진 듯도 싶기는 하지만(그래서 처음 배우는 사람은 더 헷갈릴 듯 싶다), 이 뒤에 나오는 키워드 들은 일종의 데이터 들을 제한하는 키워드 들이다. 요즘의 여러 다양한 빅데이터 솔루션들의 개념으로 생각하자면 필터링 역할의 키워드라고 보면 될것 같다. 실제로 사용하는 키워드들도 서로 비슷하고 말이다.

여기서도 전통적으로 보던 키워드와 현대의 프로그램 구조를 반영한 키워드들이 서로 어울려 있는 느낌이 있다. IN 이나 NOT IN인 같은 경우는 여러가지 중에 하나를 선택하는 구문이다. 앞의 예제에서 연봉이 3500이나 4000인 직원을 찾을 때 아래와 같은 정직한 쿼리를 쓸 수도 있겠지만 

SELECT name, salary FROM employees 
WHERE salary = 3500 OR salary = 4000;





실제로는 아래의 IN 쿼리가 더욱 간단하고 가독성도 좋다. 

SELECT name, salary FROM employees 
WHERE salary IN (3500, 4000);




저 IN 뒤에는 특정 값 뿐만 아니라 응용에 따라 다른 테이블의 제한된 조회 결과를 가져오는 서브 쿼리도 가능하다. 아래와 같이 2개의 테이블을 이용해 베스트셀러 테이블에 있는 과자들에 대한 정보만 가져오는 쿼리를 만들 수도 있다. 

SELECT snack_name, price, calories
FROM snacks
WHERE snack_name IN (
    SELECT snack_name 
    FROM best_sellers
);





BETWEEN ... AND 도 숫자나 날짜를 찾는 용도로 사용된다. 아래의 간단한 예제를 보자.

SELECT * FROM employees 
WHERE salary BETWEEN 3500 AND 4000;





원하는 패턴을 찾는 정규표현식의 꼬마 버전이라고 할 수 있는 LIKE 검색도 있다. "김%"는 김으로 시작하는, "%김"은 김으로 끝나는, "%김%"는 가운데 김이 들어감의 의미이다.

SELECT * FROM employees 
WHERE name LIKE '김%';





요즘은 나중에 자동화 얘기할때 설명하게 될 것 같은 정규표현식도 지원하는 디비들이 PostgreSQL, MySQL, Oracle 등도 있긴 하지만, 보통 정규표현식은 어떤 거를 찾느냐에 따라 시스템 리소스를 꽤 많이 차지 하는 작업으로 인지하고 있기 때문에 실제 현실에서는 얼마나 사용될지는 개인적으로는 잘 모르겠다. 

또한 요즘 트랜드에 맞게 JSON 을 데이터에 넣고 해당 데이터를 파싱하는 부분도 지원하고 있긴 하지만 이것도 케이스에 잘 맞게 사용하는 경우가 얼마나 많을지는 모르겠다. 요즘은 컬럼 내 json 데이터의 요소를 가상 컬럼처럼 정리해 인덱스를 걸어 속도를 높인다든지 하는 부분이 자연스럽게 추가되는 것 같긴 하다(윈도우 안의 WSL 처럼 마치 RDB 내에 작은 NoSQL 을 키우는 느낌이다). AI 쪽에서는 하이브리드 방식으로 소규모의 json 트래픽을 정합성있게 저장하는 경우 유리하다고는 말하고 있다.

CREATE TABLE snack_orders (
    id INTEGER PRIMARY KEY,
    order_info TEXT
);

INSERT INTO snack_orders (order_info) VALUES (
    '{"customer": "김철수", "items": ["홈런볼", "포카칩"], "total": 4500, "details": {"payment": "card"}}'
);

SELECT 
    order_info ->> '$.customer' AS customer_name,
    order_info ->> '$.total' AS price
FROM snack_orders;





위의 케이스들에서 공통 되게 고려해야 할 부분이 한 가지 있는데, 그게 SQL 을 보다 보면 가장 자주 듣게 되는 인덱스 이다. 인덱스는 우리가 책장에 책을 한글 이나 영어 알파벳 순으로 쉽게 찾을 수 있도록 정리해 놓는 것과 비슷하다. 근데 보통 실제로 데이터 자체를 직접 정렬하는 건 아니고(보통 일련 번호 형태인 primary키 같은 경우는 이런다고 하는 듯 하다), 다른 저장소에 데이터의 식별 가능한 전체나 일부를 순서대로 저장한 것과 비슷하다고 볼 수 있다. 그래서 우리가 특정 데이터(문구)가 들어간 컬럼을 찾으려 할 때, 순서대로 잘 정리된 인덱스 테이블을 뒤져 거기서 실제 테이블 내의 데이터의 위치를 찾아서, 전체 컬럼 데이터를 순서대로 찾지 않아도 빠르게 데이터의 위치를 찾게 된다(인덱스가 없다면 원본 데이터는 기본적으로 랜덤으로 들어오는 대로 저장되었기 때문에 처음부터 하나하나 찾아야 하지 않을까 싶다. 이렇게 하나하나 찾는 걸 보통 full scan 이라고 한다).

 

<인덱스>


특정한 한 개의 컬럼이나 몇 개의 컬럼을 묶어서 인덱스를 생성할 수 있는데, 세상에 공짜는 없어서 보통 테이블 내의 저장된 컬럼 내의 데이터 크기에 비례한 어느 정도의 공간을 인덱스 하나 당 차지하게 된다(원본 데이터를 구별할 일부 데이터를 색인 해야 하다 보니). 그래서 성능을 위해서 인덱스를 쿼리에서 요구하는 대로 한없이 늘리다 보면 배보다 배꼽이 더 커지는 일이 생길 수 있기 때문에 보통 어느 정도의 필수 인덱스들이 생성되게 되면, 이후에는 정말 어쩔 수 없는 경우가 아니면 DBA쪽에서 추가 인덱스는 잘 만들어 주려 하지 않는 듯 싶다. 특히 저장된 데이터 양이 몇 억 몇 십억으로 엄청 큰 테이블이라면, 추가 인덱스를 만드는 건 용량과 시간을 많이 잡아먹는 일이 될 테고 말이다. 

인덱스는 작은 규모의 데이터를 가진 환경에서는 그다지 그 효용성을 인지하지 못하게 될 수도 있기 때문에, 디비를 전문적으로 관리하는 사람이 없는 회사에서는 프로그램이 인덱스를 잘 지키지 않고 호출할 수도 있다(어떤 경우는 인덱스 자체가 거의 없었다는 상황도 들어봤다). 해당 부분이 규모가 작을 때는 별 일이 없이 살다가 데이터의 규모가 커지면서 인덱스를 사용하지 않는 쿼리들의 결과가 느려지고 추가로 데이터베이스의 자원을 대부분 차지하게 됨으로서 전체 사이트의 속도를 느리게 만들 수 있는 바오밥 나무 같은 일이 생길 수도 있다. 또 그 정도로 관리하는 데이터베이스였다면 아마 앞에서의 여러 테이블들의 상관 관계도 그렇게 확장성에 유리하게 설계도 안 되었을 가능성도 높을 것이고 말이다. 앞에서 쿼리와 테이블을 이해하려면 디비를 호출하는 프로그램도 어느 정도 이해하는 게 좋다고 했는데, 반대로 바깥 세상의 프로그램을 잘 이해하려면 호출할 대상인 디비 내부의 테이블과 쿼리를 어느 정도 이해하는 게 반대로도 필요하다고 생각한다.





다음으로 WHERE 과 직접접인 관계는 없지만 앞에서 나온 OVER, WITH 같은 쪽이나 결과의 정렬 부분에 대한 키워드가 있다. 이건 엑셀의 데이터 정렬 기능을 생각하면 완전히 동일하다. 

밑의 예제는 임직원 데이터에 대해서 연봉은 높은 순서(DESC)로, 만약 연봉이 같다면 차 순위인 이름이 빠른 순서(ASC)로 정렬해 보여준다. 

SELECT name, salary
FROM employees
ORDER BY salary DESC, name ASC;





마지막으로 언급할 부분은 결과를 제한 하거나 건너 뛰는 부분이다. 실제 쿼리를 날리다 보면 데이터가 너무 많이 나와서, 필요한 데이터만 몇 개 보고 싶을 수 있다. 역사적인 이유 때문에 데이터베이스 별로 LIMIT(SQLITE, MySQL)나 TOP(MSSQL), ROWNUM, FETCH FIRST(ORACLE) 등으로 약간 씩 문법이 달라 처음에 다른 데이터베이스를 쓸 때 괜한 혼란을 주곤 한다. 거의 모든 데이터베이스에 있을 OFFSET 문법은 정렬된 순서의 데이터에서 특정 숫자를 건너 띄고 조회하는 게시판의 페이징 쿼리 등에서 사용된다(이 키워드가 없다면 프로그램이 전체 데이터를 가져와 화면에서 나눠서 보여줘야 할 텐데 생각만 해도 아찔하다). 아래는 기존 테이블에서 연봉이 높은 사람 순서로 2명(LIMIT)을 가져오는데, 가장 연봉이 높은 1명은 제외(OFFSET)하고 가져온다.

SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 2
OFFSET 1;





이렇게 되면 SQL 의 기본적인 문법이 왜 그렇게 구성되었는지는 아는 부분은 대략 설명했다고 생각이 든다. 설명한 방식이 좀 공감이 갔음 하는 바램이 있고, 서당개 식으로 배운 거기 때문에 혹시 틀린 내용이 있더라도 양해를 해줬음 한다. 다음 시간에는 좀 예전 스타일의 구조 같긴 하지만 AI와 의논하며 앞에서 얘기했던 책 대여점 프로그램에 필요한 테이블들을 만들고, 데이터를 넣고, 쿼리를 만들면서 이제까지 설명한 부분들을 녹여 복습하고 문법 설명에 대한 마무리를 할까 한다. 또 SQLITE 도 한번 설치해 보고(다른 디비와는 달리 SQL 서버라기 보다 편집기가 설치되는 느낌이긴 하지만) 데이터가 실제 컴퓨터에 어떻게 저장 되느냐도 간단하게 봐보자.

 - Fin -

posted by 자유로운설탕
2026. 3. 2. 18:04 프로그래밍

이번엔 한번 RDB(Relational Database)가 존재하는 의미라고도 할 수 있는 "관계"에 대해서 생각해 보자. 개인적으로 SQL 의 관계에 대해서 잘 설명해 줄 수 있는 키워드 들은 JOIN, UNION, GROUP BY 라고 생각해서 세 개의 키워드의 개념이 왜 존재 하는 지에 대해서 설명해 볼까한다.

 

우리가 누군가와 썸을 탄다고 생각해 보자. 그렇게 되면 아마도 그 사람하고 뭔가 "연결(JOIN)"이 되는 것을 바라게 될 것이다. 무언가와 연결을 되려면 공통된 부분이 있어야 할테고, 우린 아마 상대방과 연결 될 수 있는 어떠한 연결 고리를 찾기 위해서 안테나를 세우게 될 것이다. 그래서 무언가가 혹시 닿게(ON) 된다면 썸이 연애로 발전하게 될 수도 있게 될 테이고 말이다. 두 개의 존재 사이에서 연결을 가져올 수 있는 무언가 동일한 것을 찾는 것처럼, SQL도 두 개의 테이블 간에서 같은 데이터들을 연결하는 기능을 제공하고, 이것을 JOIN 이라는 키워드를 사용해서 나타낸다. 실제 필드 에서는 여러 개의 테이블이 복합 해서 조인이 되거나, 특별한 관계를 뽑아내기 위해 자기 자신하고 조인을 하는 경우도 있긴 하지만 기본 적인 관계는 2개의 테이블에서 시작된다고 볼 수 있다.

<썸 타기>




사실 SQL 의 조인에는 INNER, LEFT, RIGHT, FULL, CROSS 등이 있다곤 하지만, 개인적으로는 한번에 다 머리 속에 억지로 넣어 놓고 헷갈리는 것보다는 INNER 와 LEFT 를 잘 이해하면 나머지는 실제 쿼리 들을 보거나 공부하면서 천천히 응용 방식을 익히면 된다고 생각한다. LEFT JOIN 을 보게 되면 SQL 에서 한번 쯤 꼭 만나야 할 NULL 의 의미도 생각해 볼 수 있게 되고 말이다. 관계형 이라는 말처럼 개별 문법을 익히는 게 중요한 게 아니라 어떤 유익한 점이 있어 그런 개념을 SQL 이 포함했을까를 고민하는 것이 맞다고 본다.

일단 JOIN 은 앞에서 봤던 엑셀 표 모양을 상상해 보면 2개의 테이블에서 각각 하나의 컬럼을 선택해서 두 개 안에 담긴 데이터 사이에서 동일한 값을 찾게 된다(기술적인 언어이기 때문에 유사어 같은 걸 찾진 않고 완전히 같은 데이터 값인 경우 매칭하게 된다). 매칭이 되면 테이블들의 하나의 줄 자체가 서로 매칭이 되는 거고, 만약에 한 쪽에 여러 개의 같은 데이터가 있다면 아래와 같이 그 수 만큼의 줄이 생기게 된다. 매칭이 안 되는 양 쪽 테이블의 모든 데이터 행 들은 썸 탈 때의 서로 상관없는 관심사처럼 INNER JOIN 에서는 아무 관심이 없다. 저 관계는 두 개의 테이블에 담긴 기준이 되는 같은 데이터가 어떤 것이냐에 따라서 의미를 가지게 된다. 

 

<Inner JOIN>




일단 JOIN의 시작은 앞의 시간에서 얘기했던 SELECT 문을 기본으로 진행 된다고 보면 된다. 앞에서 나왔던 SELECT 문을 생각해 보면 아래와 같은 형태이다. 낯설다면 방안의 서랍에서 물건을 찾아오던 그림을 떠올려 보자.

SELECT 대상 FROM 테이블 WHERE 조건

 


예를 들어 한 쪽 테이블에 회원 정보(회원 아이디, 성별, ...)가 있고, 다른 쪽에 구매 내역(회원 아이디, 구매 물품, ....)이 있다면 두 개를 조합하게 되면 특정 남자 회원들이 조합한 구매내역들을 뽑을 수가 있다. 예를 들면 이런 식의 쿼리가 될 것이다.

SELECT "구매상품들" FROM "회원정보 테이블" 
JOIN "구매내역 테이블" ON "회원정보의 구매아이디와 구매내역의 구매아이디 매칭" 
WHERE "회원정보의 성별이 남자인 경우"



또는 한쪽 테이블에 부서 정보(부서 코드, 부서 이름)가 있고, 다른 쪽에 직원 정보(직원 이름, 직원 아이디, 부서 코드)가 있다면 아래와 같이 사용할 수도 있다.

SELECT "부서 이름 및 사용자 이름" FROM "부서 테이블" 
JOIN "사원 테이블" ON "부서 테이블의 부서 아이디와 사원 테이블의 부서 아이디 매칭" 
WHERE "사원 이름이 홍길동"



결국 만능 키워드는 아니기 때문에 결국 사용하는 몇 가지 패턴이 있기는 하지만, 관계 자체만이 아니라 어떤 데이터가 저장되어 매칭 시키느냐에 따라서 사용하는 의미가 달라질 수 있다. 앞의 구매내역을 체크하는 곳에서도 SELECT 문에 "구매 상품들" 대신에 "구매 아이디" 넣는 다면 남자 회원들이 구매한 상품을 찾는게 아니라, 상품을 구매했던 남자 회원들을 찾는 쿼리로 의미가 바뀔 수도 있다. 결국 쿼리는 테이블 간의 관계 설계와 그 테이블이 현실과 매칭되는 의미에 따라서 비슷한 형태라도 의미가 달라질 수 있기 때문에, 해당 데이터의 해석에 따른 의미를 자연스럽게 가진다고 봐도 된다. 그런 관점이 없음 의미가 없기도 하고 말이다. 그래서 앞에서 얘기했던 SQL 을 이해하려면 데이터를 넣는 어플리케이션 단도 같이 이해해야 한다는 얘기도 그런 부분을 돕는 맥락이라고 이해를 해보자.

위의 회원정보와 구매 내역 쿼리를 설명했던 대목을 AI 한테 부탁해서 쿼리를 만들어 보면 아래와 같은 점잖은 SQL 쿼리가 나오게 된다. 테이블 뒤에 "AS 닉네임"으로 가정 하는건 쿼리의 가독성도 높여주지만, 두 개의 테이블에 같은 컬럼이 있을 경우에 구분할 수도 있고(디비에 따라서 임의로 해석해 주는 경우도 있지만, 어떤 디비는 명확히 구분해 호칭이 모호하다고 에러를 내기도 한다), 아까 얘기했듯이 자신과 자신을 JOIN 할수도 있기 때문에 임의의 네이밍을 붙이는게 여러모로 의미가 있다. 이것 저것 덕지덕지 추가됬긴 하지만, 여기서 위의 말로 표기한 의미가 대충 표현된 것이 이해된다면 성공했다고 볼 수 있다. 다른 분야도 마찬가지지만 이후 나머지는 자꾸 봐서 익숙해 지는 수밖에 없다.

SELECT 
    m.User_ID,
    m.Gender,
    p.Product_Name,
    p.Purchase_Date
FROM Member_Info AS m
JOIN Purchase_History AS p ON m.User_ID = p.User_ID
WHERE m.Gender = 'Male';




다음엔 LEFT JOIN을 함 이해해 보자. 개인적으로 첨에 봤을 때 INNER 조인, LEFT (OUTER) 조인 등의 차이가 많이 헷갈렸었는데, 지금 생각해보면 그게 왜 필요한지 잘 맘에 안 닿아서 그랬을 것도 같다. LEFT JOIN 은 위의 썸 얘기를 다시 빌려온다면 짝사랑에 가깝다. 썸은 서로 관심이 있어서 연결될 가능성이 높지만, 짝사랑은 상대의 마음을 전혀 모른다고 볼 수 있기 때문에 대부분 맹목적일 것이다. 상대의 모든 것을 신경쓰고 그 하나하나에 의미를 두게 될 것이기 때문이다. 

LEFT 조인은 비슷하게 내가 중요하다기 보다는 나의 왼쪽에 있는 상대의 모든 것을 기준으로 나를 판단하는 JOIN 형태라고 보면 된다. 뭐 현실적으로 보면 자신도 소중한 존재인건 맞지만 짝사랑에 빠지게 되면 상대의 모든 점에 대해서 내가 의미가 있는지를 살펴 보게 된다. 그리고 내가 그 상대가 가진 점에 대해서 특별한 연결 점을 찾지 못한다면 해당 부분의 나는 의미 없게 느껴지는 일도 생길 수 있다. 요 의미 없는 부분이 SQL 에서 얘기하는 NULL 이라고 봐도 된다(Null 은 순수 영어 뜻으로는 아무 힘이 없거나 비어있음을 얘기한다). JOIN 관계에서의 NULL은 상대에 비해서 아무 의미가 없다는 뜻으로 보게 되면 무방하지 않을까 싶다. 

 

<Left JOIN>


그래서 LEFT JOIN 의 의미도 위의 JOIN 과 비슷하게 여러가지 맥락을 가질 수 있다. 한 쪽이 회원 테이블이고 다른 한 쪽이 구매 테이블인 상태에서 LEFT JOIN을 한 후 구매 테이블의 회원 ID 가 NULL 인 줄(테이블 2개가 합쳐진 줄이긴 한다)을 조건으로 단다면 물품 구매를 안 한 회원을 찾아낼 수 있다. 일반적인 문장으로 표기한다면 아래와 같을 거다.

SELECT "회원정보 테이블의 구매회원들" FROM "회원정보 테이블" 
LEFT JOIN "구매내역 테이블" ON "회원정보의 구매아이디와 구매내역의 구매아이디 매칭" 
WHERE " 구매내역 테이블의 구매아이디가 NULL 인 경우"



위와 같이 진중한 쿼리로 만들어 본다면 아래와 같을 것이다.

SELECT 
    m.User_ID
FROM Member_Info AS m
LEFT JOIN Purchase_History AS p ON m.User_ID = p.User_ID
WHERE p.User_ID IS NULL;



또한 왼쪽 테이블의 데이터를 전체 표시하면서 해당 데이터에 해당되는 특정 조건이 없는 지를 전체 펼쳐서 체크해 보는 경우에도 사용할 수는 있을 것이다. 왼쪽 테이블의 데이터는 항상 모두 기본으로 가져가고 싶을 때 유용할 수도 있다.



다음에는 UNION을 생각해보자. UNION 은 정석으로 생각한다면 서로 형태가 같은 2개의 테이블을 하나의 테이블처럼 합쳐서 볼 수 있게 하는 거지만, 보안 취약점 공격인 SQL Injection에서 자주 일어나는 패턴까지 고려해 본다면, 테이블의 형태가 꼭 같을 필요는 없는 것 같고, 서로 다른 테이블 사이에서 같은 의미를 가진 데이터(SQL 인젝션 공격할 때는 수단과 방법을 안 가리기 때문에 이런 관계도 무시하긴 한다) 컬럼을 모아서 하나의 테이블을 만든다고 보면 될 것 같다. 아래와 그림과 같이 서로 다른 종류의 환경을 가진 사람들이지만 줄다리기라는 목표를 가지고 힘을 모아 동맹(UNION)을 결성해서 하나의 세력(SQL 에서는 TABLE)처럼 만들어 진다고 보면 어떨까 싶다.

<줄다리기>




테이블 뿐만 아니라 테이블에서 특정 컬럼을 모아서 SELECT 를 하면 하나의 결과 테이블이 된다는 확장 적인 관점에서 보면 UNION의 모습은 아래와 같다. 그림에서 만약 2개의 테이블이 완전히 동일하다면, 두 개의 테이블이 하나로 합쳐진 결과를 받을 수도 있다.

 

<UNION>


그럼 이런 형태의 관계는 어떤 의미가 있을 수 있을까? 예를 들어 테이블 형태가 같은 경우는 과거나 현재 테이블 또는 시간대 별 테이블을 합치는 경우가 있을 수 있다.

SELECT 동일 데이터 FROM "현재 테이블" 
UNION 
SELECT 동일 데이터 FROM "과거 테이블"



WHERE 조건은 각각의 SELECT 문이 있기 때문에 각각에 걸 수 도 있고, 두 개가 합쳐진 테이블에 통합해 걸 수도 있다. 두 개를 합칠 경우는 나중에 SELECT 추가 설명에서 얘기할 서브 쿼리 개념이 생기게 된다. 처음엔 좀 헷갈릴 수 있지만 쿼리의 반환 결과도 결국은 테이블 형태이기 때문에 그 가상의 테이블에 다시 쿼리를 날릴 수 있다고 보면 맘에 편하다. 이 무한 루프 이면서 재귀 호출 같은 부분이 SQL 문법의 강력함 중에 하나인 것 같고 말이다. 누구 표현으로 성능만 고려 안 한다면 어떻게든 원하는 데이터를 보여줄 수 있다고 하기도 한다.

SELECT 특정 조건 FRON
(
SELECT 동일 데이터 FROM "현재 테이블" 
UNION 
SELECT 동일 데이터 FROM "과거 테이블"
) AS "통합 테이블"
WHERE "통합 테이블 기준 조건"



위의 예제를 AI 한테 물어봐 만든 쿼리는 아래와 같다. 해석을 해보면 과거와 현재 거래 테이블에서 사용자와 금액을 모아서 10000만원 이상의 건만 찾는 것이다. 

SELECT 
    Total_List.User_ID,
    Total_List.Amount
FROM (
    SELECT User_ID, Amount AS Total_Amount
    FROM Sales_Present
    UNION ALL
    SELECT User_ID, Amount AS Total_Amount
    FROM Sales_Archive
) AS Total_List
WHERE Total_List.Total_Amount >= 100000



뭐 SQL 엔진이 어떻게 데이터를 찾아오려 할지(실행 계획이라고 한다)는 따져봐야 할 테지만, 현재 조건에서 저 거래 테이블에서 모든 데이터를 일단 다 가져오는 게 성능 적으로 맘에 걸린다면 아래와 같이 미리 각 테이블에서 100000만원 이상만 뽑아서 합치는 쿼리를 만들 수도 있을 것이다. 아마 요즘 SQL 엔진들은 프로그램 컴파일러 비슷하게 왠만하면 알아서 등가적으로 유리한 실행 계획을 세우긴 할 것 같긴 하다. 여튼 뭐 이런 부분은 비즈니스 설계가 데이터베이스 어떻게 녹아져 있고, 어떤 의미의 쿼리를 만들기를 원하냐에 따라서 달라질 것은 같다.

SELECT 
    Total_List.User_ID,
    Total_List.Total_Amount
FROM (
    SELECT User_ID, Amount AS Total_Amount
    FROM Sales_Present
    Amount >= 100000
    UNION ALL
    SELECT User_ID, Amount AS Total_Amount
    FROM Sales_Archive
    Amount >= 100000
) AS Total_List



또는 테이블 형태가 다른 경우도 있을 수 있을텐데, 괜히 설명만 길어지는 듯해서 그건 앞에서 본 예제에서 형태가 좀 다른 테이블에서 같은 형태의 컬럼을 SELECT 해서 나온 2개의 가상 테이블을 UNION해 합친다고 상상해 봄 될 것 같다. 이건 나중에 후반에 보안에서 SQL의 해석이 어떻게 필요한지에 대한 얘기를 하나 쓸거 같아서 그때 자세히 예제를 보자. 

현실에서 테이블 형태가 다른 경우라면 서로 형태가 다른 2개의 직원 테이블에서 공통된 정보(이름, 부서, 이메일)를 가져오는 경우가 있을 수 있을것 이다. 위의 예제에서도 Sales_Present와 Sales_Archive의 실제 컬럼이 완전히 같지 않더라도 User_ID, Amount 컬럼이 있거나 의미가 같은(컬럼 이름은 형식일 뿐이고 JOIN 에서 얘기했지만 데이터의 모양과 값 자체가 중요하다) 컬럼 들이 있다면 같은 이름으로 하나의 테이블을 만들 수 있을 것이다. 그래서 앞의 서로 다르지만 같이 힘을 모을 수 있는(데이터의 형태가 같은) 사람들이 모인 줄다리기 그림을 UNION 이라고 표현해봤다. 그리고 중복된 데이터를 제거하느냐의 차이를 가진 UNION ALL 이라는 키워드도 사족으로 있다.



마지막으로 테이블 간의 관계는 아니지만, 테이블 내의 데이터의 묶음 관계를 나타내기 때문에 현재의 글에서 같이 설명했음 하는 게 GROUP BY 이다. 가끔 여러 의미로 우리가 사회나 회사에서는 하나의 통계치로 나타날 뿐이라고 얘기하긴 하지만, GROUP BY 로 묶이는 순간에 그런 일들이 자동으로 이루어 지게 해준다. 

처음 GROUP BY 문을 만들게 되면 SELECT 문에 들어가는 컬럼이 왜 갑자기 이상하게 제한되게 되어 에러가 나는지에 대해서 헷갈리게 느낄 수 있는데, 이건 앞에서 얘기한 것 같이 GROUP BY 의 대상 이외에는 모두 통계적 익명성이 되기 때문 이라고 볼 수 있다.

<그룹>




위의 그림을 보면 우리가 해당 그림은 남자, 여자의 그룹 관점으로 보는 순간부터 남자 셋, 여자 셋이나, 남자들의 키의 평균, 여자들의 키의 평균, 남자들의 나이 합, 여자들의 나이 합 등으로 이루어질 수 밖에 없다. 그래서 GROUP BY 를 하는 순간 해당 GROUP BY 의 대상이 되는 컬럼은 그 대표성을 나타내는 남자, 여자 같은 키워드 역할을 하고, 나머지 컬럼 중에 통계적 의미를 가져올 수 있는 컬럼들은 SUM, COUNT, AVE 같은 수학 함수와 만나서 수치를 표기 하게 된다. 

왜 이렇게 답답하냐고 생각할수도 있지만 GROUP BY 가 집계를 기초로 한 통계 데이터를 쿼리를 통해서 쉽게 가져오기 하기 위한 것이기 때문에 어쩔수 없다고 생각하자. 그림으로 표시하면 아래와 같은 형태라고 볼 수 있다.

<GROUP BY>



말로 표시하자면 아래와 같을 것이다.

SELECT "분류를 원하는 데이터", COUNT, AVE, SUM 등(수치를 담은 컬럼이나 행 자체) 
FROM "집계 대상 테이블" 
GROUP BY "분류를 원하는 테이터가 들어가 있는 컬럼"



여기에서 "집계 대상 테이블" 에 추가로 제한을 넣고 싶다면 WHERE 문을 뒤에 넣음 된다. GROUP BY 를 통해 만들어진 테이블에 대해서 WHERE 조건을 넣으려면 조금 또 헷갈리는 키워드가 존재하는데, HAVING 이라는 키워드가 나온다. 위의 예제를 보면 컬럼 이름 이외에는 모두 통계적 수치이기 때문에 HAVING 에도 통계적 표현이 들어가야 해서 아마 좀 처음엔 의아할 것이다.

SELECT "분류를 원한는 데이터", COUNT, AVE, SUM 등(수치를 담은 컬럼이나 행 자체) 
FROM "집계 대상 테이블" 
GROUP BY "분류를 원한는 테이터가 들어가있는 컬럼" 
HAVING "같은 집계함수"



AI 를 통해 만든 아래의 쿼리를 보면 그룹에 등록한 20살 이상인 사람만 대상으로 스터디 그룹 별로 묶어서 평균 점수가 90점 인 스터디 그룹들만 "스터디 그룹 이름", "멤버 수", "평균 점수"를 표시해 준다. 

SELECT 
    Study_Group_Name,
    COUNT(*) AS Member_Count,
    AVG(Score) AS Average_Score
FROM Student_Registry
WHERE Age >= 20 
GROUP BY Study_Group_Name
HAVING AVG(Score) >= 90;



추가로 저 GROUP BY 도 2개 이상의 컬럼을 넣어서 특정 그룹안의 특정 타입 등으로 상세 분류로 집계할 수도 있겠지만(지역 별 스터디 그룹의 통계 숫자라든지) 시작은 위의 GROUP BY 개념에서 출발한다고 생각하고 찬찬히 따져보면 된다.



위에서 3개의 대표적인 관계를 나타내는 키워드 개념들을 설명했지만 사실 저건 어찌 보면 키워드 자체가 중요한 게 아니라 데이터를 해석하는 몇 가지 방식을 설명한 것이라고 보면 될 것 같다. SQL 은 그걸 쿼리로 테이블 안의 데이터를 지정해 쉽게 가져올 수 있게 구현한 개념이고 말이다. 여러 SQL 서버들은 그 개념들을 구현한 실체 들이다. 완전하게 같진 않지만 엑셀에서도 VLOOKUP 이라든가, GROUPBY, 피벗 테이블이라든지 vstack 같은 비슷하게 있다고 하고, 많은 데이터를 분석하는 툴에서도 마찬가지로 비슷한 관점의 기능이나 표현들이 있을 것이다. 

또한 위에서 얘기했듯이 기법은 데이터의 의미에 따라서 적용되는 패턴은 같지만 해석이 달라질 수 있다고 본다. 그래서 사실 적용하는 기법 이전에 중요한 게 데이터의 수집과 현실과 적절히 매칭된 설계라고 생각한다. 개인적으로는 현실의 모든 상황을 반영하는 완벽한 데이터가 세상에 존재할 수 있는지에 대해서는 조금 회의적이긴 한다. 이건 아날로그가 디지털로 완벽히 호환 가능하냐는 문제이기도 한 것 같고, 궁극적으로는 현실도 하나의 디지털 세상일 수 있을까 하는 문제하고도 연관되지 않을까 한다.

그래서 SQL 이 프로그램 세계에 기반한 엄청 기술적인 분야이기는 하지만, 반대로 보면 바깥의 데이터 수집 측면까지 고려했을 때 어느 정도 100% 완벽하진 않지만 확률이나 통계적으로 의미 있는 데이터의 저장과 해석을 추구한다는 시작점에서 지원되는 문법들을 봤으면 하는 생각도 있긴 하다. 또 나중에 이것과 다르게 데이터를 해석하려 하는 다른 툴과 기법들도 계속 관심을 가지면 좋겠다는 생각도 하고 말이다. AI 또한 그런 강력한 해석 툴 중에 하나라고 본다. 사실 뭐 이런 얘기에 대해서 근거에 기반에 얘기할 만큼 충분히 이 쪽 분야를 이해는 못한다고 생각하기 때문에 걍 개인적인 밤 시간의 지나가는 상념으로 봐줘도 무방할 듯은 하다.

- FIN -

posted by 자유로운설탕
2025. 12. 21. 15:09 프로그래밍

뭐 요즘 들어 AI, 가상 현실 이런 식으로 명시적으로 미래 지향적으로 얘긴 하지만 생각보다 컴퓨터 속의 세상은 처음 생겼을 때부터 현실과 많이 닮은 방식을 지향하며 이루어지지 않았나 싶다. 사람도 태어났을 때부터 다른 사람들이 만들어 놓은 현실에 적응하면서 살고 있고, 컴퓨터라는 가상의 도구 또한 딱히 그 현실을 벗어나서는 의미가 없을 것 같기도 하기 때문이다. 요즘은 반대로 데이터 안의 세상이 현실의 여러가지 규칙과 물리적인 부분들을 대체할 수 있다고 얘기하고 있고 말이다. 

그런 측면에서 보면 데이터베이스에 질문을 던지는 쿼리는 우리가 현실에서 물건을 찾을 때와도 비슷하다고 볼 수 있다. 아래와 같이 내 방안에 있는 서랍 안에서 야구 공을 가져오도록 다른 누구에게 부탁 한다고 생각해 보자. 아마도 "우리 집에 가서 내 방에서 서랍장이 있는데 위에서 두 번째 서랍을 열어보면 오른 쪽에 야구 공이 하나 있을 거야" 라고 표현할 수 있을 것이다. 이걸 쿼리에서 쓰이는 용어로 대충 치환해 보면 "특정 서버의 특정 데이터베이스에서 특정 테이블을 찾아서 특정 위치에 있는 특정 컬럼의 정보를 가져와 줄래?" 와 비슷하다.

[물건 찾기]




또 하나 생각할 점은 해당 기술이 영어를 기반으로 만들어졌으므로 영어에서 쓰이는 여러 등가되는 단어로 구성되어 있다는 것이다. 예를 들어 가져와 라는 표현은 "select" 로, 어디에서 라는 단어는 "from" 으로, 특정 위치에 있는 이라는 부분은 "where" 이라는 단어로 표현된다. 

일상에서 쓰는 자연스러운 영어 표현은 아니겠지만 예문을 위해 강제로 AI 한테 만들어 달라고 해 살짝 편집한 문장은 아래와 같다. 

SELECT the baseball FROM the chest of drawers WHERE it is inside the second drawer

 


키워드를 빼고 나머지 부분을 위에 얘기한 한글로 바꾸면 아래와 같이 될테고,

SELECT "야구공" FROM "서랍장" WHERE "2번째 서랍 안에서"

 

 

전문적인 SQL 문으로만 구성하면 이렇게 될거다.

SELECT "컬럼(들)" FROM "원하는 테이블" WHERE "원하는 값을 가져오는 조건문"

 


위와 같은 느낌으로 쿼리를 만든다면 대충 아래와 같이 될 수 있다. 

SELECT baseball FROM chest_of_drawers WHERE drawer_number = 2

 


이제 마지막으로 우리가 보통 보는 소문자 쿼리로 바꿔보자. 이제 이렇게 봤을때 select, from, where 이라는 단어는 SQL 에서 쓰는 고정된 문법 단어로 보이고, 나머지는 우리가 부탁할 때 사용하는 선택 할 수 있는 값으로 보인다면 성공한 것이다. 선택 가능한 저 값들도 아무거나 들어갈 순 없고 정해진 장소를 묘사하는 특정한 의미를 가진 표현들만 들어갈 수 있다는 느낌도 조금은 들 것이다. 이 부분 때문에 처음에 SQL이 일종의 엄청 제한된 프롬프트엔지니어링 비슷하다고 얘기를 했었다.

select baseball from chest_of_drawers where drawer_number = 2




보통 서버나 데이터베이스의 이름은 SQL 문장 내에서는 잘 보이지 않을 수 있는데, 서버는 보통 우리가 사용하는 데이터베이스 접속 용 프로그램의 첫 연결이나, 프로그래밍 언어 환경 이라면 접속을 선언하는 구문 안에 들어가 있고, 데이터베이스는 보통 UI라면 SQL 클라이언트의 접속 후에 보이는 선택 드랍 박스 쪽에 있을 수 있고, 프로그래밍 언어라면 비슷하게 쿼리 수행 전 시작 구문 안에 들어가 있을 수 있다. 

또는 데이터베이스 이름이 아래와 같이 쿼리에 끼워 있을 수도 있다. 뭐 조금 더 나아가면 데이터베이스와 테이블 사이에도 사용자라는 항목이 추가로 들어갈 수도 있지만 그건 지금은 괜히 복잡해 만 지니 생각하지 말아보자.

select baseball from my_room.chest_of_drawers where drawer_number = 2




그 다음에는 테이터를 넣는 쿼리를 생각해 보자.

이건 위의 방 예제로 설명하기에는 얘기가 꼬일 것 같고, 앞 장에서 설명했던 엑셀의 테이블 모양을 떠올려 보자. 보통 우리가 엑셀에 표를 만들 때, 각 컬럼의 이름을 만들고 거기에 값들을 한 줄 한 줄 넣게 된다. 테이블 특성 상 한 줄이 모두 채워지지 않는다면 데이터를 넣는 의미가 없을 것이다.

엑셀에 그린 테이블의 이름이 아래와 같이 상품(goods) 이고 각 컬럼의 이름에 "일련번호(sequence number-seqno)", "크기(size)", "색상(color)"이 있다고 해보자.

[goods 테이블]



마찬가지로 영어로 억지 예문을 만들면 아래와 같다. "너의 스케줄을 기입해 넣는데, 필요한 값들은 일, 휴식, 점심 요소 들이야."

INSERT INTO your schedule VALUES (Work, Break, Lunch)



해당 테이블에 값을 넣는 SQL 은 아래와 같을 수 있다.

INSERT INTO 테이블 VALUES (넣을 값 한줄)



단어 들을 영어로 표시해 보자

INSERT INTO goods VALUES (1, "small", "yellow")



테이블 옆에 컬럼 이름도 확실한 것을 원하는 성격이라면 명시적으로 넣을 수 있다.

INSERT INTO goods (seqno, size, color) VALUES (1, "small", "yellow")



힙 하게 특정 컬럼에만 값을 넣게 할 수도 있다. 이 구문의 경우에는 나중에 좀더 자세히 얘기하겠지만, 테이블을 처음 어떻게 만들었냐에 따라 생략한 컬럼 값 들이 특정 디폴트 값이나, 자동으로 증가되는 번호, NULL 이라고 하는 빈 의미의 값이 들어갈 수도 있다. 

INSERT INTO goods (size, color) VALUES ("small", "yellow")



역시 키워드 들을 소문자로 바꾸면 아래와 같이 많이 보는 쿼리 문이 된다.

insert into goods (size, color) vaules ("small", "yellow")





데이터의 삭제도 영어 문장에서 시작해 보자. 


"쇼핑 리스트에서 가격이 50달러가 넘는 아이템들을 지워줄래?"

DELETE items FROM the shopping list WHERE the price is over fifty dollars.



앞의 goods 테이블이라면 노란 색을 지정해서 지울 수 있을 것이다.

DELETE 대상 FROM goods WHERE color = "yellow"



한 가지 select 와 비슷하지만 다른 부분이 있는데, select 는 내가 원하는 특정 컬럼 만을 선택 적으로 가져올 수도 있지만, delete 는 테이블 특성 상 하나의 열을 통채로 지워야 한다(엑셀에서 하나의 셀만 선택해 값이 아닌 셀 자체를 지울 수 없는 것과 마찬가지라고 보자) 그래서 굳이 지울 "대상"이 항상 "조건에 맞는 줄" 차체라서 굳이 언급할 필요가 없기 때문에 아래와 같이 생략되어 버리게 된다.

DELETE FROM goods WHERE color = "yellow"



마찬가지로 소문자로 표시하면 아래와 같이 된다.

delete from goods where color = "yellow"





그럼 마지막으로 대망의 테이블 만들기를 살펴 보자. 이걸 맨 마지막으로 뺀 이유는 제일 설명이 복잡하기 때문이였다. 아마 처음부터 테이블을 만드는 구문을 보고 시작하면 왜 그렇게 만들어야 하는 지가 잘 마음에 안 닿아서 SQL 공부가 싫어질 가능성이 높아질 수 있다.

한번 마찬가지로 영어 문장으로 봐보자.

"상품들이라는 이름의 새 테이블을 하나 만들어 주는데, 일련 번호, 사이즈, 색상 이라는 필드로 구성되게 해줘."

CREATE a new table, named 'goods', CONSISTING OF fields for Serial Number, Size, and Color.



SQL 문은 () 기호를 사용해서 조금 더 심플하게 정리해 표현한다.

CREATE TABLE "상품들" (일련번호, 사이즈, 색상)




그리고 조금 더 이해하기 어려운 부분들이 생기게 되는데, 처음으로 데이터 형의 정의라는 프로그래밍 개념이 들어가게 된다는 것이다. 사실 데이터 조회 쪽 업무나, 조회하는 프로그램만 개발한다면 아마도 이 테이블 생성하는 구문은 꽤 늦게나 만나게 될 수도 있다. DBA 같은 쪽에서 테이블 등을 다 설계해서 생성해 주고, 사용자는 데이터를 조회하거나 넣을 수만 있을 가능성이 높기 때문이다. 사실 삭제 쿼리의 권한 또한 보통 데이터 조회만 하는 비즈니스 업무라면 거의 만날 수 없을 수도 있다.

왜 굳이 복잡하게 이름만 정하면 됬지 데이터 형의 정의라는 게 필요하냐고 하면, 그게 RDB 의 설계 철학이기 때문이다. 그래서 나중에 얘기할 NOSQL 을 보면 이 단점을 없애기 위해서 엄청 약하게 규제를 풀어준다(데이터 형이 없다는 건 아니고 내부적으로 유연하게 대처해 자동으로 처리한다고 해 보자). 대신 자유도가 높음 그만큼 또 데이터의 정확한 모습을 이해하기가 어려울 수 있다는 단점도 있다고 보기 때문에 세상에 항상 장점이 있음 그만큼 단점도 생긴다고 봐보자. 특정 장점을 위해서 특정 단점을 어느 정도 트레이드 오프해 감수했다고 생각하면 편하다.

데이터 형의 정의를 반영하면 위의 문장은 아래와 같이 된다. 컬럼의 정의에 관련된 몇 가지 키워드 들이 있기 때문에 컬럼을 보통 가독성을 위해 한 줄씩 표기 한다.

CREATE TABLE "상품들" (
   데이터 형태가 숫자인 일련번호
   데이터 형태가 문자인 사이즈
   데이터 형태가 문자인 색상
)



테스트도 가변적(varchar)이냐 고정(char) 형태냐도 따지는 경우도 많지만 그건 뭐 나중에 보도록 하고, 위의 문장을 SQL 에서 약속된 문장으로 바꿈 아래와 같이 된다. AI 에게 나중에 실습 할 SQLITE 문법으로 만들어 달래 봤다. 밑의 데이터 형을 나타내는 키워드는 SQL 별로 조금 명칭이나 범위가 틀리다. 앞에서 얘기한 SQL 의 엄격함과 NoSQL에서 그 일부의 엄격함을 버리고 다른 장점을 차용한 것을 생각해보면, SQL 을 구현하는 데이터베이스 사이에서도 그런 약간의 차이가 있다고 보면 된다.

밑을 보면 상품들(goods) 라는 테이블을 만들면서, 일련번호(seqno)는 정수(INTEGER) 데이터 형으로, 크기(size)와 색상(color)은 텍스트(TEXT) 데이터 형으로 정의한 것을 볼 수 있다. 컬럼 간을 구분하기 위해 쉼표도 생겼다.

CREATE TABLE goods (
    seqno INTEGER,
    size TEXT,
    color TEXT
);



SELECT 도 마찬가지지만 이렇게 모든 게 끝나면 좋을 텐데, 여기에 테이블의 컬럼 값들을 편하게 다루기 위한 몇 가지 요소들이 추가된다. 이건 사실 여기에서 다루기에는 너무 얘기가 산으로 갈 것 같아서, 실습에서 만나게 되면 그때 상황에 맞는 설명을 하기로 하고 자주 볼 수 있을 개념들만 짧게 얘기하자.

  • 아까 INSERT 시 지정되지 않은 컬럼에 특정 값을 자동으로 넣게 해주는 DEFAULT 키워드가 있고 
  • 특정 컬럼이 반드시 값이 들어가야 한다고 정의하는 NOT NULL, 비어도 상관 없다고 정의하는 NULL 키워드도 있다
  • 회원 ID 같이 이게 무조건 중복되지도 말아야 하고, 비어있지도 않아야 한다는 PRIMARY KEY 라는 개념도 있고
  • 자동으로 데이터가 들어올 때마다 일련번호를 넣어주는 AUTO_INCREMENT, IDENTITY, SERIAL 이라는 키워드도 있고
  • 들어가는 값을 체크해 제한하는 CHECK 라는 키워드
  • 처음엔 조금 이해하기 헷갈리지만 다른 테이블의 같은 의미의 컬럼과 묶어서 무결성을 보장하려는 FOREIGN KEY 개념 등등이 있다.


사실 이런 키워드 들은 실제 테이블의 설계와 호출하는 프로그램의 관계 들을 보면서 이럴 때 이렇게 잘 쓰는구나를 느껴야지 잘 잊어버리지 않게 되는 경향이 있기도 하고 앞에 얘기했듯이 DBA팀이 실제 있는 회사 같은 경우에는 실습 때 빼고는, 실전에서 테이블 생성 권한을 가지는 경우도 드물 것이기 때문에 나중에 실습 할 때 적당히 상황을 만들어 끼워서 이해하도록 해보자. 

위의 키워드 들을 적당히 버무려 넣으면, 아래와 같이 많이 복잡해 보이는 테이블 생성 문이 만들어지게 되지만 위에서 얘기 했듯이 이 요소들은 좀 더 테이블을 무결성 있게 잘 관리하기 위한 유틸리티 들을 장착한 것이라고 보면 된다. 

CREATE goods (
    seqno INTEGER PRIMARY KEY AUTOINCREMENT,
    size TEXT NOT NULL CHECK (size IN ('small', 'midium', 'large')),
    color TEXT DEFAULT 'black',
    quantity INTEGER CHECK (quantity >= 0)
);



예를 들면 자동 증가 옵션(AUTOINCREMENT)이 없으면 일일이 다음 숫자를 계산하고 입력 시에도 동시에 들어오는 다른 쿼리와의 우선 순위를 고려해서 넣어줘야 하며, 빈 값이 들어가지 않거나(NOT ULL), 고유한 값이 들어가도록(PRIMARY KEY) 프로그래밍 하는 쪽에서 신경 써줘야 하는데, 이게 해당 테이블을 사용하는 불특정의 개발자들이 잘 이해해서 실수 없이 수행 하기에는 꽤 골치 아픈 일이 되기 때문에 데이터베이스 쪽에서 알아서 안전하게 챙길 수 있게 유용한 기능을 만들었다고 보면 된다. 

뭐 정말 간단하게 다른 예와 비교하면 회의실 예약 시스템이 누가 예약을 하던 중복 예약은 못하게 막는 것과 비슷하게 기본 설계 로직 같은 게 들어갔다고 봐도 된다.


여기까지 왔을 때 잊지 말아야 하는 중요한 부분은 위의 복잡해 보이는 생성 쿼리도 사실은 처음 얘기한 아래의 간단한 문장에서 시작한 거라는 것이다. 그럼 복잡해 보이는 모양에 홀려 길을 잃을 가능성이 적어진다고 본다.

CREATE TABLE "상품들" (일련번호, 사이즈, 색상)



여기까지 오면 SQL 책에서 맨날 얘기하는 어려워 보이는 CRUD(Create, Read, Update, Delete) 에 대해서 기초를 다 이해를 했다고 봐도 된다. 무엇보다도 문법 적으로 딱딱해 보이는 부분에 대해서 좀 거부감이 사라졌기를 기대한다. 물론 저 SELECT 와 FROM 사이의 원하는 컬럼을 지정하는 부분과 WHERE 키워드 뒤의 위치를 찾는 부분에 몇 가지 variation 을 만들어 주는 추가 키워드들이 있긴 하다. 하지만 그건 앞의 CREATE 문에서 좀 더 안전한 테이블 데이터의 관리를 위해 NOT NULL, DEFAULT 같은 여러 키워드들이 추가된 것처럼, 좀 더 정교하고 의미 있는 조회 쿼리를 만들어 내기 위한 애드온 기능이라고 생각하면 맘에 편할 것이다. 


추가 키워드 들과 얽힌 SELECT 문을 더 깊이 들어가기 전에 이젠 하나의 테이블을 벗어나서 테이블 간의 관계를 따지는 키워드 들을 먼저 살펴봐야 할 것 같다. 다음엔 테이블 간의 관계에 대한 키워드들이 왜 나왔을 지를 살펴보도록 하고 너무 길어지기 전에 이번 글은 마무리를 하려 한다.

- Fin -

posted by 자유로운설탕
2025. 12. 21. 14:58 프로그래밍

앞에서 데이터베이스에 대해서는 최대한 얘기 안 하고 쿼리에만 집중하겠다고 했었지만, 막상 데이터베이스에서 시작을 안 하면 쿼리에 대해 설명을 하긴 어려울 듯 해서 데이터베이스 부터 우선 언급을 하려 한다. 

데이터베이스라는 것은 기술적으로 접근하면 꽤 실체를 만나기 어려운 뒷 부분에 존재 한다고 볼 수도 있지만, 혹시나 자영업 또는 알바 경험을 통해서 무언가를 팔거나 빌려주는 일을 해보았다면 외부에서도 쉽게 만나 볼 수도 있을 형태의 흔한 개념일 수도 있다고 생각한다. 

꽤 오래전의 일이긴 하지만, 요즘은 많이 없어진 한 때 붐이였던 책 대여점 알바를 한 적이 있었다. 아는 사람을 돕는 일이 여서 처음 대여점을 차리는 것부터 시작해서 얼마간 운영했던 기억이 있다. 아마 더 옛날이라면 만화방(요즘의 만화 카페)에서 책을 빌려줄 때 장부에 항목을 적어서 빌려 주고, 반납하면 해당 항목을 줄을 그어 지우는 가게들도 많이 있었다(요즘도 만화 카페 운영 프로그램에서 따로 지원하지 않는 이상 아직도 그렇게 아날로그로 운영 하는 경우도 있을 거라고 본다). 

책 대여점의 경우는 주 업무가 사람이 와서 보는 만화 카페와는 다르게 책을 빌려주고 받아서 수익을 내는 부분이기 때문에 아무래도 장부에 적어서 해당 일을 하기에는 몇 천권이 되는 책을 빌려주고 돌려받기엔 효율이 나오기 힘들었다. 게다가 미 반납 시의 연체료 등 세세한 것까지 종이 장부에 적어가면서 운영했다면 아마 해당 대여점 체인들이 그렇게 발달하지 못했을 것이다(그래서 가끔은 어떤 분야는 기술이 꼭 뒷 받침을 해줘야 비로서 꽃을 피우는 것 같다).

때마침 컴퓨터 하드웨어 가격도 가게에서 살 수 있을 만큼 어느 정도 보급형으로 가격이 내려온 상태라서, 책 대여점을 차리는 사람을 타겟으로 책 대여 프로그램을 만들어 파는 사람이나 업체들이 생기게 되었다. 우리나라에 ISBN 코드가 도입이 막 되던 시점 이였기 때문에 지금같이 공공 API를 지원하지는 않았을 것 같고(게다가 그 때는 거의 인터넷이 없이 돌아가는 프로그램 들이라서 아마 공공 API를 호출해 정보를 업데이트 한다는 것을 생각도 못했을 때였을 거 같다), 만화책의 경우 해당 ISBN이 없는 해적 판도 종종 있는 상황이였기 때문에 자동화를 향한 과정은 꽤 힘든 일이였다. 

우선 프로그램 회사에서 준 바코드를 책에 붙이고 스캔 하면, 해당 바코드를 키로 가진 책 정보 등록 메뉴가 나오는데, 거기에 제목부터 종류, 대여 가격 등 필요한 정보들을 새로운 책이 생길 때마다 반복해 작업 해야 했다. 또한 새로운 고객이 오면 회원 정보 메뉴를 열어서 이름, 전화번호 등 주요한 정보들을 등록해야 했고 말이다(이 번거로움은 지금도 거의 비슷해 보이고, 그래서 명시적인 형태를 줄인 카카오톡 인증을 통한 가입 같은 게 생겼을 테고 말이다). 뭐 지금 바라보면 아까운 시간이라고 생각되겠지만, 그 시절을 미화하자면 지금 시대의 데이터 라벨링이나 업무 정보를 데이터화 하는 작업과 비슷하게, 효율적인 자동화 인프라 구축을 위한 필수적인 일이였다고 볼 수 있다.

이후 해당 고객이 방문하게 되면, 이름을 물어 회원을 찾고, 이후 대여 버튼을 누르고, 바코드를 스캔을 하면 해당 손님이 대여한 책으로 등록 되고, 반납할 때도 비슷하게 진행 했다. 예전에 종이 장부에서 일일이 오래된 날짜를 찾아서 전화를 걸어 독촉하고 연체료를 찾는 일도, 알아서 연체된 책들을 보여주고 연체료도 계산해 주는 기능도 있었다. 

위의 얘기를 듣게 되면 데이터베이스를 경험해보고 사용해 본 사람들은 데이터베이스 내에 있는 관련 테이블들이 그려지고, 책 정보를 입력을 하거나 수정하는 부분, 삭제하는 부분, 전체 회원 목록을 보는 부분, 해당 회원이 대여한 책들을 표시하는 부분, 연체가 된 책들을 보여주고 연체료를 계산 하는 부분 등이 머리 속에 대충 그려 질 것이다. 위의 책 대여 프로그램이 어찌보면 현대의 데이터베이스의 초기 모형을 이용해 만들어진 데이터베이스 기반의 프로그램이라고 볼 수 있다. 

물론 요즘 이런 프로그램을 만든다면 데이터를 어떻게 효율적이게 저장할 지에 대해 스스로 코드를 짤 일은 아마 없을 거고, 상황에 따라 파일 또는 서버 형태의 오픈소스 데이터베이스를 하나 살짝 넣어서 호출하는 프로그램만 개발해서 제공할 것이다. 실상 현실의 많은 상용 솔루션들도 도입 비용 등을 낮추는 등 여러 목적을 위해서 여러 오픈소스 데이터베이스를 기반으로 구현하는 경우도 많은 편이다(물론 상용 데이터베이스도 같이 지원하거나 성능이나 폐쇄적 보안 등의 특수한 이유로 자체 데이터베이스를 구현하거나 오픈소스를 기반으로 수정 개발해 사용하는 경우도 있다).



그럼 (관계형) 데이터베이스는 앞에 얘기했던 책이나 회원, 대여 상황, 대여 금액 등에 대한 정보들을 어디에 저장하게 될까? 그 장소가 데이터베이스 책을 처음 펴게 되면 아마 맨 처음에 나오게 될 테이블(Table)이라고 보면 된다. 영어를 쓰는 서구권 사람들 입장에서는 많은 기술적인 언어들이 영어로 이루어진 단어나 문법을 기반으로 일반 문장과 비슷한 흐름을 가지도록 만들어져 있다. 그래서 하나 하나의 용어를 무작정 외우는 것 보다는 이유가 있는 네이밍을 한 경우도 많을 것이므로 해당 추상적인 부분을 상상하게 익히는 것도 나쁘진 않아 보인다. 

데이터베이스에서 쓰는 테이블이라는 개념은 우리가 일상에서 자주 보는 무언가를 올려 놓는 물리적인 개념의 테이블은 아니고, 가상의 데이터를 올려놓는 "Time Table(시간표)" 같은 표현에서 쓰는 "표" 라는 의미를 가지고 있다. 현재 비즈니스 쪽에서도 많이 사용하는 데이터 시각화 도구인 태블로(Tableau)도 이 계열 뜻을 표현한다고 볼 수 있다.



한번 컴퓨터가 없을 때 우리가 앞에서 얘기한 일들을 어떻게 수행 했을지 상상해 보자. 아까 장부 얘기가 나왔지만 장부는 일종의 데이터 요소들을 정리해 한눈에 정리해 보고 상황을 파악하고 업데이트 하기 위한 표의 일종이라고 볼 수 있다. Table 의 어원을 찾아보면 평평한 상 위에 체크 무늬 천을 깔고 거기에 물건을 구분하여 넣어서 계산을 해서 표의 의미도 가지게 되었다고 한다. 

어찌 보면 시각화 된 추상화라고도 볼 수 있는데 우리가 또 흔히 볼 수 있는 컴퓨터 내의 프로그램과 비교해 본다면 컬럼(column)과 로우(row)로 이루어진 작은 칸으로 가득 찬 사각형의 엑셀과 비슷하다. 엑셀도 장부처럼 생긴 네모난 테이블이 하나의 시트를 이루어 여러 개의 시트로 구성되어 있기 때문에, 데이터베이스와 모양이 비슷하다. 물론 기능의 완전한 대체라든지, 사용의 효율성에 대해서는 별개의 문제이긴 하지만, 두 개 다 기본적으로는 데이터를 표 형태로 효율적으로 다루고자 하는 서로 다른 방향의 시도인 것은 맞긴 하다. 또한 엑셀은 항상 눈으로 보면서 계산 및 시각화를 하는 데에 강력한 장점을 가져서 시각 적으로 보며 추상화를 하며 비교적 친숙하게 쓸 수 있지만, 데이터베이스는 직접 조회해 샘플 데이터를 보거나, 테이블의 구조를 살펴 보기 전에는 안의 모양을 알 수 없기 때문에 좀 더 추상적인 접근이 필요 할 수 있다.

다만 머리 꼬리를 떼고 생각한다면, 여러 개의 데이터를 분류해 담는데 필요한 테이블 들(RDB 에서는 아래 그림과 다르게 사각형이긴 하다)을 그리고, 각 테이블 들을 독립적이거나 또는 서로 연결하면서 의미 있는 정보 구조를 추출해서 사용하는 것이 관계형 데이터베이스의 전부라고 봐도 될 듯 싶다. 나머지는 그걸 잘 하게 만들어 주는 여러가지 도움 기능이라고 보자. 

[시간표 만들기]





데이터베이스의 테이블 들은 자꾸 살펴 보다 보면 머리 속에 대충 관계가 정리가 되어 그려지게 되지만, 현실에서의 문제는 저런 테이블이 작은 회사의 경우는 수 백 개부터, 큰 회사의 경우는 수천, 수 만개 까지 늘어날 수 있게 된다는 부분이다. 회사의 대부분의 비즈니스 정보가 저 안에 들어가기 때문에, 회사 내의 데이터 전체를 유기적으로 이해할 수 있는 사람은 보통 손에 꼽을 정도라고 생각한다. 

게다가 저 부분을 잘 이해하려면 데이터베이스 안의 테이블들 만을 이해해야 하는 게 아니라, 그 데이터를 넣어주는 프로그램 로직이 왜 그런 형태로 데이터를 넣는 지를 이해해야 하는 경우도 많기 때문에, 데이터베이스의 테이블과 컬럼(뭐 물론 프로시저나 함수, 뷰 등의 다른 이해에 필요한 요소들도 많겠지만 일단 이렇게 단순화 하자)및 그 연관 관계를 모두 이해했다는 것은 회사의 비즈니스의 흐름을 이해했다는 것과 비슷하게 되기 때문이다. 그래서 비즈니스와 어플리케이션의 로직을 이해 못한 채 데이터베이스 만을 보게 된다면 데이터 자체는 볼 수 있지만 그 의미를 알지 못하는 난감한 상황도 생길 수 있다(이게 개발자들이 어느 정도 데이터베이스를 이해하면 좋듯이, 데이터베이스를 다루는 사람이 어느 정도 해당 데이터베이스를 사용하는 프로그램 측면을 이해하면 좋은 하나의 이유라고 생각한다. 데이터베이스에 쌓이는 데이터는 두 개의 오케스트레이션의 결과 같은 거라고 보기 때문이다.).

그래서 우리가 살펴 보려는 SQL 은 저 수많은 데이터들을 테이블을 이용해 구조적으로 담고 종횡무진 연결하면서 우리의 비즈니스에 필요한 여러 값들을 적절하게 조회해 와서 사람이나 프로그램들이 사용하게 해준다. 그러다 보니 상황에 따라 엄청 복잡해 보이고 이해하기 힘든 쿼리 들을 보는 일도 있게 되겠지만, 개인적으로는 테이블들의 역할 및 관계만 잘 이해하고 있다면 엄청 암호와 같이 복잡한 엑셀 셀의 셀 번호와 함수들이 섞인 정의를 보는 것보다는 해석하기 쉽다고 생각한다. 그래서 작은 규모의 테이블들과 연관된 쿼리부터 하나하나 이해해 가다 보면 어느새 꽤 익숙해진 자신의 모습을 발견할 수 있게 될 것이다. 또한 일반적인 프로그래밍 언어 보다는 자유도가 많이 작기 때문에, 그렇게 광활한 세상에서 헤맬 필요도 없다(SQL 만 보는 목표로 한다는 가정을 하면 오픈 월드 게임과 일반적인 게임의 차이 정도라고 본다).

그럼 데이터베이스가 어떤 것이고, 데이터를 담는 테이블이 왜 필요한 지에 대해서 공감이 갔기를 바라며 다음 글에서는 좀 더 실제 적인 얘기를 해보려 한다.

- Fin -

posted by 자유로운설탕
2025. 12. 21. 14:54 프로그래밍

이번엔 영역을 조금 바꿔서 SQL 에 대해서 이야기를 해보려고 한다. 뭐 생각하기에 따라서 SQL 도 Structured Query Language의 약자이니 기술적 일 뿐 여전히 언어의 범주에 속한다고 생각하고 접근해도 될 듯하다. 그리고 이왕 얘기를 한 김에 SQL과 대칭 된 듯이 보이는 Nosql 쪽에 대한 의견도 정리해 전달해 볼까 한다.

제목을 데이터베이스의 이해라고 하지 않고 SQL의 이해라고 한 이유는 그렇게 깊은 곳까지 들어갈 자신도 없고, 데이터베이스 자체 까지 얘기하다 보면 글의 목적도 많이 벗어나게 될 것 같기 때문이다. 그래서 쿼리를 통해 데이터베이스에 데이터를 저장도 하고 조회도 하는 SQL로 범위를 제한하려 한다. 

이 글의 대상은 쿼리를 어떻게 사용하는지 관심이 있는 비즈니스 쪽 분야이거나(전통적으로 이쪽에서 사내 데이터를 받아 분석하다 직접 퀴리를 날리기 시작하면서 데이터 분야 쪽으로 전직하는 경우도 있고, 요즘은 AI 검색 엔진의 도움을 받아서 좀 더 허들을 쉽게 넘는 경우도 종종 보이는 듯 싶다), 또는 게시판 같은 프로그램 들을 만들면서 간단한 쿼리들을 만들어 봤지만 아직 SQL 의 전체적인 그림은 보이지 않는 기술 쪽 사람들 정도가 될 것 같다. 

이미 SQL을 익숙하게 쓰며 디비를 다루어 봤던 분들은 이 글을 굳이 보실 필요는 없을 것 같지만, 만약 MSSQL, Oracle, MySQL 등의 관계형 디비(RDB)만을 사용해 왔다면, 뒤에서 쿼리 관점에서 간단히 비교해 다룰 MongoDB나 Elastic Search, Redis 에 대한 이야기는 한번 읽어봐도 괜찮을 것 같다고 생각한다.



그럼 일단 본격적인 얘기를 시작하기 전에 왜 굳이 SQL을 배우는 게 IT 세상에서 살아가는데 도움이 될 수 있다고 생각하고 있는지와, 요즘 같이 AI에게 하고자 하는 일과 함께 잘 물어보면 쿼리를 잘 만들어 주는 상황에서 굳이 SQL을 처음부터 이해를 할 필요가 있는 지에 대해서 당위성을 살펴보자.



우선 SQL은 데이터베이스나 그와 비슷한 시스템에 쿼리를 날리는 언어라고 볼 수 있을 것이다. Query라는 단어는 무언가 질문을 던지는 의미라서, 요즘 AI 엔진에 질문을 날리는 것과 동일하다고 볼 수 있다. Structured 라는 것은 무언가 구조화된 것을 나타내므로, 현재 언어 기반으로 질문을 던지는 AI 에 비해서는(물론 이것도 어찌 보면 자유 도는 있지만 언어라는 틀에 갇혀 있다고 볼 수 있다. 말도 안되는 말로 질문을 하면, 말도 안되는 답이 올 테니까 말이다) 약간 고지식하게 제한된 단어와 문법을 이용해서 던지게 된다. 

어찌보면 AI 쪽에서 얘기하는 프롬프트 엔지니어링처럼 질문을 받는 대상 엔진이 학습을 한 형태와 결이 맞아, 이해하고 답을 잘 낼 수 있는 형태의 질문을 던지는 거라고 보면 된다. 다만 SQL 쪽은 해당 부분이 훨씬 더 엄격해서(이건 프로그래밍 언어도 그렇다) 조금만 문법이 틀리거나 사전에 정의된 키워드를 사용 안 하면 에러를 내버린다. 사실 프로그래밍 언어를 포함해 이 엄격함 부분이 입문자 들을 힘들게 만드는 요소이긴 하다.

SQL이 질문을 던지는 대상인 데이터베이스는 말 그대로 데이터(data)의 기반(base)이다. 컴퓨터가 생기고 하드 디스크 형태의 저장소(아마 처음엔 테이프 형태였겠지만)가 처음 생겼던 시점부터, 데이터는 어떤 형태로 든 저장되어 왔었었다. 데이터베이스는 해당 데이터를 파일 안에 구조적으로 잘 저장하고, 구조적으로 잘 조회해 오는 데에 특화된 프로그램 형태라고 볼 수 있다. 

실제로 우리가 얘기하는 많은 형태의 SQL, NoSQL 서버들은 데이터들을 파일 안에 바로 저장하거나, 속도를 위해 메모리 안에 파일 형태 비슷하게 저장했다가, 최종적으로는 파일 형태로 저장해서 영구적으로 데이터를 보호하고 제공한다고 보면 된다. 그 저장하고 가져오거나, 조금 더 나아가 데이터베이스 서버에게 특정한 정렬 및 연산을 통해서 우리가 원하는 결과 셋을 가져오게 하는 공식적이고 표준적인 언어를 SQL 이라고 보면 된다. 



그래서 IT를 이해하려면 반드시 사람 또는 기계, 환경으로부터 시작된 여러 데이터들이 흐르게 되고, 그 흘러다니던 데이터가 결국 어딘가에 다음 처리나 영구적인 보관을 위해서 체계적으로 저장되어야 하는데 그게 데이터베이스 형태이고(물론 뭐 여러가지 캐싱이나 카프카 같은 메시징 큐, 로그 파일 등의 중간 형태의 저장도 있긴 할 테지만 그건 기본적인 걸 이해한 후, 다음 단계의 주제니 나중에 고민해 보자), 데이터베이스를 실무적으로 이해하는 방법은 데이터베이스를 설치해 보고 Query 날려 이해하는 것이라고 본다. 또한 다음 시리즈로 다루고 싶은 자동화를 위한 스크립팅이나 프로그래밍 분야의 응용을 위해서도 이 SQL에 대한 이해는 필수 불가결한 주제라고 본다.

데이터 관련 테스팅을 하거나 하거나 보안 업무를 진행하려 한다면, 해당 주제들을 이해하지 못한다면 거의 아무 것도 못한다고 할 수 있다. 물론 테스팅이나 보안 같은 지식들이 당연히 기본으로 동반 되어야 할 테지만 그것은 대상을 어떻게 해당 분야의 관점으로 해석하느냐에 대한 문제이고, 기본적으로 해당 대상이 무엇인지 모른다면 아무것도 할 수 없는 상황이 된다. 좀 더 최신 분야를 얘기하자면 AI 보안 쪽 일 것이다. 데이터 분석이나 AI를 전혀 모르는 보안 인력이 해당 분야에 가서 뭔가 의미 있는 일을 하기에는 같이 일하는 사람들의 아주 큰 배려와 특정 수준까지 올라오기를 기다리는 인내심이 없다면 거의 힘든 상황이 될 것이다. 

그래서 데이터의 이해, 자동화, 개발 또는 데이터베이스와 관련된 테스팅, 보안 등을 잘 하기 위해서는 기본적으로 이해해야 되는 부분 중 하나가 SQL(+NoSQL) 이라고 생각한다. 그래서 개인적으로 SQL 이라는 주제는 IT 와 관련된 직업의 어느 시점에서든 꼭 어느 정도 이해하고 넘어가면 좋을 것 같은 주제라고 생각을 한다.



다음으로는 AI의 고도화로 인한 지식의 대체 측면에 대해서 생각해보자. 여기에는 전문가들에 비해서 특별한 소양이 있는 건 아니니 때문에, 사용자 관점에서 얘기를 해보려 한다. 개인적으로 현재 상태의 AI 엔진에 대해 의인화를 하자면 엄청 똑똑하고 아는 게 많지만, 뭔가 평균적이고, 모범적인 느낌이 있고, 가끔 자신도 모르게 거짓말을 섞는 사람이다. 악의는 없기 때문에 또 뭐라고 할 수는 없는데, 그렇다고 그대로 믿기에는 불안한 측면이 있다.

AI 엔진의 결과에 대해서 적절한 사회적 책임을 지우기 위한 논의가 활발하다고는 하지만, 과연 AI 엔진의 생산자가 뻔히 가끔 악의 없는 거짓말 들을 한다는 것을 아는 상태에서(사실 선의/악의 자체가 인간의 기준과는 다르기도 하고 학습 데이터의 담긴 메시지에 의해 결정 나는 편일 것이기 때문에 의도를 따지는 게 별 의미는 없을지도 모른다) 그 책임을 선뜻 맡을 것 같지는 않다. 보험 같은 리스크 분산 형태로 일부 해결될진 모르지만 그런다고 피해자가 생기는 근본적인 문제를 막을 수는 없을 테고, 그런 잘못된 결과에 대해 책임을 지우는 것은 AI 산업 자체의 발전에 큰 제약을 가져올 수도 있을 테고 말이다.

지금 같이 일상적이거나 일부 기술적인 부분에 대해서 질문을 하는 경우에 대해 크게 부작용이 보이지 않는 작은 헤프닝인 것처럼 느껴지지만, 만약 의사 자격이 없는 사람이 뛰어난 AI 엔진에게 문의해 답을 받아서 해당 지식을 가지고 의사 활동을 한다고 하면 누가 그 사람을 인정할 수 있고, 어떻게 그 책임을 질 수 있겠냐는 극단적인 상황을 생각해보면 작은 일은 아닌 듯한 느낌도 든다. 프로그래밍 쪽도 마찬가지로 요즘 같이 모든 기반 산업 및 서비스 등이 소프트웨어에 기반해 돌아가는 측면에서 앞에 예를 든 의료 쪽 문제에 비해서도 그렇게 가벼운 주제는 절대 아닌 듯 하다. 

요즘 개발자 쪽에서 AI 검색으로 얻은 코드를 보기에 잘 돌아간다는 이유로 추가적인 검증 없이 가져다 코드에 넣는 경우도 느는 듯 하지만, 테스팅이나 보안의 관점에서는 이런 행위가 너무 용감하다는 느낌도 드는 측면도 있다(물론 잘 짜여진 프로세스에서는 후속 된 유닛 테스트, 코드 리뷰나 기능 테스팅 등이 해당 문제를 잘 막을 가능성도 있다). 하지만 언젠가 많이 쓰이는 오픈 소스에서 특정한 결함이 있는 코드가 발견되어 해당 코드를 쓰는 수많은 시스템을 변경해야 되는 이슈가 생기는 문제처럼, AI 엔진이 추천해서 만든 코드에서 그런 문제가 생기진 않는 다는 보장은 누구도 못할 듯 하긴 하다.



그럼에도 불구하고 예전 구글 검색 엔진이 생겼을 때처럼 현재의 AI 검색 엔진 또한 무시하고 살아가기는 그 효용 성을 보면 현실적으로 어렵다고 본다. 그럼 어떻게 AI 검색 엔진 사용에 접근 해야 될까? 개인적으로 생각했을 때는 AI 검색 엔진은 특정한 초기 학습을 돕는 목적이 아니라면, 직업 적으로는 자기가 잘 아는 분야에 대해서 질문을 던지는 방식으로 써야 한다고 생각한다.

아까의 의인화 측면에서 얘기하자면 같이 지낼 수 밖에 없는 어떤 대상이 좋은 말을 많이 하지만 의도 되지 않은 거짓말을 가끔 섞어 쓴다면(만약 진짜 의도적이라면 그 사람을 피해야 하겠다...), 그 거짓말에 영향 받지 않도록 내가 상황을 관리하는 수밖에 없다. 해당 부분이 가능 하려면 상대의 상황을 이해하고, 내가 알고 있는 그 분야의 지식과 비교해서 상대가 그렇게 말하는 이유를 추측하고, 신뢰할 수 있는 지를 판단할 수 밖에 없다. 그렇게 되려면 자신이 질문하려는 그 분야를 스스로 잘 이해할 수 밖에 없다고 본다. 

약간 아이러니 하지만 AI 엔진이 제공해주는 추론 적이거나 고차원적인 언어적 쿼리의 혜택을 최대한 누리려면, 내가 그 분야를 AI 엔진이 얘기해 주는 수준과 적어도 비슷하게 이해하는 것이 중요하다고 본다(이런 부분이 AI 시대가 직업을 처음 시작하는 주니어에게는 불리하고, 결과를 비판적으로 볼 수 있는 자기 생각의 기준이 있는 시니어에게 유리하다는 얘기의 일부 측면과 연결되는 게 아닌가도 싶다).

또한 AI 의 발전도 물론 중요하겠지만 한 인간으로써, AI가 새로운 학습을 하듯이 우리도 AI 에게 계속 질문을 던지면서 성장해 가야 한다고 본다. 마치 과거에 구글 검색엔진에 올라온 수많은 사람들의 얘기를 비판적으로 종합해 이해하면서 기술자들의 내면이 성장했듯이 말이다(개인적으로는 요즘 AI 엔진의 경우 그런 데이터들의 학습을 기반으로 성장했을텐데, 실제의 결과에서는 그 사람들의 특이한 관점들이 점점 사라져서, 나중에는 반대로 AI가 학습할 만한 그러한 지식의 반딧불들이 혹시나 멸종되어 버리진 않을까 하는 생각도 해본다). 

그래서 우리가 SQL 을 잘 이해하고 데이터를 저장하고 분석하고, 조회하는 데에 사용하고 싶다면, 우선 SQL을 어느 정도 이해하고 해당 부분을 기반으로 검색 엔진이든 AI 검색이든, 책이든, 타인과의 교류 등을 통해서 자신이 이해한 모델을 확장시켜 나가야 한다고 생각한다. 좀 우스꽝스러운 생각일지도 모르지만 AI 를 사용하는 우리 자신도 하나의 불완전하지만 이상적이고 계속 변할 수 있는 모델이라고 생각하기 때문이다. AI를 신기해 하고 앞으로의 발전을 기대하는 것도 좋지만 우리 자신의 내면의 모델을 강화 시키는 측면에서 AI를 관찰하고 사용하는 것도 나쁘진 않다고 생각한다. 

그래서 AI가 아무리 SQL을 잘 짜 주더라도, 그걸 스스로의 기존 지식에 의해서 잘 이해하고 사용하는 게 스스로나 해당 결과에 영향을 받을 수 있는 외부 시스템이나 사람들에게도 좋기 때문에 SQL 이 무언 지에 대한 자기 생각 정도는 이해해 하나 정도 가지고 있는 게 좋지 않을까 생각한다. 엔지니어로서 기술이 이해는 안 되지만 잘 동작하는 마술같이 보인다면 큰일이라고 생각한다. IT는 반드시 왜 그렇게 동작 하는지 설명이 가능해야 하는 분야이기 때문이다. 여하튼 위의 2개의 이유 때문에 이 글을 시작하게 되었다고 얘기해 봤는데 공감이 되었을진 모르겠다.

[마술의 원리를 이해하기]




그럼 앞으로 진행할 방식을 얘기해 보자. 우선 할 얘기가 아주 많진 않을 듯 해서 아마 5~8 편 정도로 끝날 것 같다. 아무래도 이 주제는 실습이 없음 의미가 없을 듯 해서 고민해 보다가 MSSQL이나 MySQL 등을 직접 설치해서 하게 되면, 주제인 SQL 과 크게 상관없는 너무 세부적인 것들을 설명하게 되 버려서 처음 경험하는 분들의 집중력을 해칠 것 같고, 다른 방법으로 무 설치로 웹에서 실습 할 수 있는 방식도 찾아보았으나 조회 쿼리 등은 가능하지만 직접 데이터를 넣지는 못하는 것 같아서, 결국 SQLite 라는 로컬 파일 형식의 미니 디비를 사용해서 이야기를 풀어보려고 한다(일부 프로그램 설치가 필요하지만 앞의 다른 디비들에 비해서는 정말 간단해 관심 있는 사람이면 충분히 따라할 수 있을 것 같이 판단 되었다).

아마 얘기하고 싶은 왠만한 SQL 요소는 거기서 다 얘기할 수 있을 테고, 요즘 맥만 있는 분들도 많으니 예전 책에서의 아쉬움을 생각해서 가능함 처음 세팅 부분에서는 윈도우와 맥 환경의 실습 환경 세팅을 모두 설명해 보려 한다. 전체 글에 걸쳐 가능한 웹이나 AI 검색 엔진의 설명에서 많이 얘기하고 있는 기술적인 관점의 설명보다는, 기존 글과 이어진 언어라는 관점에서 해당 SQL 문법들이 데이터베이스와 소통하는데 필요하게 된 상황을 가능한 쉽게 설명해 보려 한다. 다만 이미 개인적으로는 어느 정도 기술적 뷰의 당연함에 젖어있는 마음이라 잘 될지는 모르겠다(항상 그렇지만 한번도 경험하지 못한 상대방의 입장을 고려해 설명하는 부분은 생각보다 너무 어려운 일인 것 같다).

이런 기본적인 SQL에 대한 이해 부분이 잘 넘어가면, 이 후에 MSSQL 이나 MySQL 같은 다중 사용자 환경에서의 데이터베이스들을 소개하며 간단히 SQL 관점에서 어떤 차이들이 있는지 얘기하려 한다. 여기까지 무사히 마무리가 된다면, 이후에 조금 주제가 어려워 지지만 이 기회가 아님 연결해 얘기하기 힘들다고 생각해서 MongoDB, ElasticSearch, Redis 같은 NoSQL이 왜 나오게 되었을까에 대한 개인적인 생각과 이 녀석들이 기존의 RDB와 어떻게 다르게 데이터를 저장하고 사용하는 지에 대해서 짧은 설명 들을 하며 마무리를 하려 한다. 

- Fin -

posted by 자유로운설탕
2025. 11. 15. 21:38 일본어와 중국어

이젠 두 개의 언어를 비교하면서 하고 싶었던 얘기는 대충 다 한 듯 해서, 마지막 잡다한 글로 일단 마무리를 하려 한다. 언젠가 새로운 경험치가 쌓여 다른 얘기를 쓰고 싶어짐 혹시나 이어질 지도 모르겠지만 말이다.

 


우선 한국 사람 입장에서 일본어, 중국어 두 개의 언어를 공부할 때의 장 단점은 어떤 게 있을까? 개인적으로 생각했을 때는 아무래도 일본어와 한국어는 고유어와 한자어가 섞여 있는 구조여서, 한자어를 꽤 많이 사용하는 편이기 때문에 한자라는 공통 점에서 중국어와 만나게 된다. 그렇게 되면 한자 자체에 대해 좀 더 자세하게 살펴 볼 수 있는 기회가 오는 것 같다. 마치 영어 공부할 때 궁금해서 그리스어, 라틴어 어원 책들을 보듯이, 약간 스타일은 다르겠지만 비슷한 느낌으로 접근해 볼 수도 있는 것 같다.

반대로 앞에서 비교한 것처럼 한자의 뉘앙스 면에서는 꽤 차이가 나기 때문에 혼란을 주는 요소들도 분명 많다. 입장은 상대적이라고 느끼는 게, 강사 분하고 얘기하다 보면 우리가 중국식 한자, 일본식 한자 얘기하듯이, 그 쪽도 한국식 한자, 일본식 한자 표현이라고 얘기하는 걸 보면 재밌기도 하다. 하지만 어차피 뜻 글자이고 각각의 언어와 얽혀서 변화해온 한자들이기 때문에, 어찌 보면 사람의 문화와 상상력에 따라 다른 방향으로 자란 한자라는 뜻 글자들을 살펴볼 수 있다는 다양성의 측면도 있는 것 같다. 

또 언어가 늘어날 수록 컨텐츠가 늘어날 가능성이 많기 때문에 좋아하는 장르(음악, 영화, 애니 등등)가 있다면 그만큼 자신의 컨텐츠 범위가 확장 될 수 있다. 개인적으로는 그렇게 못해서 아쉽지만 다양한 언어로 자유롭게 쓰거나 말할 수 있다면 자신을 표현할 수 있는 한국어로만 갇히지 않은 영역에도 뛰어들 수 있을 거고 말이다. 물론 그런 단계까지 가기가 쉬운 것은 아니고, 세상일이 그렇듯 무언가 좋아하는 거를 계속 하는 것은 운이나 환경도 어느 정도 작용하는 것 같기도 하다. 그래서 사실 여기에서는 일본어, 중국어에 대해서만 제한해서 얘기했지만 언어를 배우는 것을 좋아하는 사람들은 여러 언어를 배울 수록 그러한 장점이 늘어날 것이다. 개인적으로도 여전히 중국어의 늪에서 허우적 대는 듯해 다른 언어는 엄두도 못 내고 있지만, 살살 다른 언어를 가볍게 시작해 보고 싶은 생각을 요즘 하고 있다.



얘기했듯이 두 개의 언어 모두 읽는 거만 좀 낫고 말하는 건 별로인 편이기 때문에, 이런 상태에서 해당 나라로 여행을 갔을 때의 느낌을 얘기해 보려 한다. 앞에서 얘기했지만 사실 일본은 10년전에 한번, 중국은 금년 초에 한번 겨우 몇 일간만 다녀왔기 때문에 기억도 가물가물 하며, 특별히 깊이도 없는 여행 이였다고 볼 수 있다. 다만 운 좋게 두 번다 주위에 친했던 그 나라 사람들이 있었어서, 많이 에스코트를 받기도 했고, 혼자 갔음 경험하기 힘들 었을일도 경험하는 장점이 있었었다.

개인적으로 생각할 때, 두 나라의 글을 어느 정도 읽을 수 있어서 외국 글자라는 위화감이 적게 있다면, 도쿄나 상하이에서의 느낌은 그냥 좀 말이 잘 안 통하는 서울 같은 느낌이였다. 도시의 풍경도 비슷하고 요즘 이런 저런 유명한 가게들은 각 나라에 똑같이 존재하는 경우가 많기 때문에 글자를 읽으면서 눈치 것 적응할 수 있다고 본다. 지하철 같은 교통 수단도 구조가 비슷하고, 출퇴근 시간이면 우리의 모습처럼 이리저리 바쁘게 다니는 직장인들이 가득하다. 또 앱 같은 경우도 그 나라의 앱을 사용(야후 지도라든지)해서 마치 우리나라의 네이버, 다음 지도를 검색해서 다니 듯이 다닐 수 있고, 약간 로컬 친화적인 앱의 인터페이스를 맛볼 수 있다. 다만 요즘 중국 현지 앱들은 위챗(微信, 웨이신)과 알리페이(支付宝, 즈푸바오)말고는 다들 중국 내 핸드폰 인증을 못하면 기능 자체를 못 쓰는 경우가 많아서 외국인이 다운 받아 쓰기는 현실적으로 힘들기 때문에 언어를 읽을 수 있는 입장에서는 아쉽기는 하다. 그리고 그런 앱들을 깔아서 보게 되면 거기에 달린 가게의 리뷰들 같은 게 모두 공부의 소재가 되기도 하는 면이 있다.

또 여행이라는 느낌도 있지만 현장 학습 이라는 느낌도 있다. 중국에 갔을 때 中心 이라는 단어가 엄청 많이 보여서 첨엔 왜 지역들을 다 중심이라고들 얘기하지 하고 어리버리하게 보다가, 아 저게 센터(Center)라는 의미구나 하고 느낀 적이 있다(무역센터, 컨벤션 센터 같이 말이다). 요런건 보고 느낌 체화 되어 안 잊혀지게 되서, 그게 뭐 외국으로 공부하러 같을 때의 장점이긴 할 테지만 말이다. 못 읽는 글들을 보면 사전을 찾아보며 아하 하는 느낌도 받게 되고, 멋드러지게 쓰여진 글을 보면 사진에 담아서 나중에 정리해 볼 수도 있다. 뭐 이리저리 얻어 들리는 말들도 있고, 간단한 인사나 계산 같은 건 적당히 더듬 거리면서 할 수도 있고 말이다. 또 개인적인 취향이지만 그 동네 노래방에 가본다든지, 서점에 가서 모양이 예쁜 책이나 좋아하는 만화책의 다른 나라 버전을 골라본다든지 하는 읽는 것과 관련된 활동도 추가 될 수 있다

주위에 혹시나 운 좋게 호감을 가진 그 나라 사람이 따라 다녀 준다면, 관광객 특유의 호구 느낌도 피할 수도 있고, 혼자 가면 놓칠 섬세한 이벤트들도 가끔 경험할 수 있고(그 동네 마트를 가본다든지, 추천하는 우리나라에선 먹기 힘든 맛있는 과일들을 골라 먹어본다든지), 관광지가 아닌 로컬의 맛있는 집도 가보는 경험도 할 수 있고 한 거 같다. 그리고 덤으로 말이 안 통해 답답해 하면서 좀 더 말이 잘 통했으면 더 친해져 볼 수 있었을텐데 하는 아쉬움과 언어 공부의 동기를 가지게 되는 장점도 생기는 것 같다. 요즘은 친해질 것 같았는데 아쉬웠다면 메신저, SNS 에 추가해서 나중을 기약해 볼 수도 있고 말이다. 그리고 또 가끔 소수의 외국인 입장이 되보는 것도 나름 타인에 대해 겸손해질 수 있는 좋은 경험이 되는 것도 같다.


마지막으로 재밌게 봤던 드라마(영화)를 몇 개 적어볼까 한다. 다만 이런 취향은 사람마다 다르기 때문에 혹시나 취향이 맞고 안 본 게 있다면 한번 찾아서 봤음 한다(개인적으로도 여러 추천 드라마들을 찾아 보다 개인적인 취향과 안 맞아서 조금 보다 만 적도 많다). 모아보고 나니 중국 드라마 쪽은 본 것이 적어 몇 편 안 되고, 일본 드라마 쪽은 옛날 드라마 위주니 그것도 양해를 바란다. 한국 제목이 있어서 마지막 편에서는 굳이 자세히 발음들을 안 적는 게 좀 더 자연스러운 마무리가 될 거 같아서 제외한다. 

[중드]

  1. 三生三世十里桃花(삼생삼세십리도화) - 무협 신선 물 같은 건데, 스토리도 괜찮았고 배우들이랑 화면이 예뻤던 듯
    琅琊榜(랑야방: 권력의 기록) - 속고 속이는 일들의 연속
  2. 三十而已(겨우 삼십) - 상하이에서 서로 다른 인생을 사는 30대 여자들의 이야기. 중국은 아직은 우리 10~20년 전 때처럼 여자가 30살이 되면 결혼에 아주 늦어진 나이라고 생각하는 것 같다. 
  3. 偷偷藏不住(너를 좋아해) - 너를 좋아하는 감정을 숨길 수 없다는 제목으로 어렸을 때 좋아하던 오빠 친구를 계속 좋아해서 사귀게 되는 얘기인데, 유치하지만 재밌었다
  4. 去有风的地方(바람이 머무는 곳) - 호텔에서 직장 생활을 잘하던 한 여자가 친구가 갑자기 시한부로 죽는 계기를 통해서 3개월간 윈낭성(원남성) 지역으로 여행을 가서 생기는 이야기. 찾다 보니 갯마을차차차를 리메이크 한 드라마라고 함. 소수 민족 문화가 있어서 색채가 예쁘다. 보다 아직 다 못 본 드라마이긴 하다.

[일드]

  1. ぼくは明日、昨日のきみとデートする(나는 내일 어제의 너와 데이트를 한다) - 한번 보고 다시 한번 보면서 시간 선들을 다시 맞춰 보게 되는 영화. 원래는 노래 불러보기 편에 나왔던 주제가(ハッピーエンド - 해피엔드)의 배경을 이해해 보고 싶어서 보게 됐었다. 개인적으로는 아주 재밌으면서도 시간의 영원함에 대해 허무해지는 영화였다
  2. 101回目のプロポーズ(101번째 프로포즈) - 기억은 잘 안 나지만 재밌었음.
  3. 踊る大捜査線(춤추는 대수사선) - 시기 상 옛날 스타일이긴 할텐데 그냥 재밌었음. 너무 막 살인 사건이 자세하게 묘사되는 형사 물이 싫다면 괜찮을 것으로 생각함. 개인적으로 일본 드라마 특유의 유머를 잘 적응 못하기 때문에 리갈하이 같은 드라마는 잘 못 본다.
  4. やまとなでしこ(야마토나데시코) - 요조숙녀 라는 한국 드라마로 리메이크 됬는데, 여배우가 통통 튀는 스타일임. 일본 노래 파트의 everything 배경을 이해하고 싶다면 보면 좋음.
  5. HERO(HERO) - 검사 이야기라는데 기억은 잘 안 나지만 재밌었음--;
  6. ハケンの品格(파견의 품격) - 보면 직장에서 말 잘 안 듣게 될지도 모름.
  7. プロポーズ大作戦(프로포즈 대작전) - 타임 슬립 로맨스 코미디 인데 재밌었음
  8. パパとムスメの7日間(아빠와 딸의 7일간) - 잔잔하면서 재밌었음
  9. ホタルノヒカリ(호타루의 빛) - 이걸 보고 퇴근 후 마시는 맥주가 맛있어 보여서, 술도 잘 안 마시는 처지에 어느 더운 여름날 마트에서 맥주를 1팩 사서 사서 시도해 봤다 몇 모금 마시고 바로 실패함. 
  10. 任侠ヘルパー(임협 헬퍼) - 야쿠자들이 노인 요양 시설 간병인을 억지로 하게 되면서 생기는 이야기. 약간 억지지만 뭐 감동은 억지에서 나오는 편이긴 하니까.
  11. 結婚しない(결혼하지 않는다) - 중드의 "겨우 서른"이 괜찮았다면, 이 영화도 괜찮을 거 같다. 왠지 비슷한 느낌 이라고 느낌.
  12. リッチマン、プアウーマン(리치 맨, 푸어 우먼) - 우리나라 드라마에서 많이 보는 엄청 부자 남자가 평범한 여자를 좋아하는 스토리였던 것 같은데 재밌었음
  13. 半沢直樹(한자와 나오키) - 은행이 배경인데 재밌었음. 위의 히어로나 춤추는 대수사선 좋아하면 같이 좋아할 듯
  14. 安堂ロイド(안도로이드) - AI 기반의 안드로이드와 사람이 섞여서 사건들이 일어남. 
  15. 重版出来!(중쇄를 찍자!) - 출판사 편집자 얘기인데 재밌었음. 원 대사가 궁금해서 일본 대본으로 보고 싶어서 대본 구해 놓고 방치되었었음. 
  16. ラジエーションハウス~(라디에이션 하우스) - 슈퍼 방사선과의 이야기, "닥터X 외과의 다이몬 미치코" 이런 스타일인데 좀 순한 맛이다.


- Fin -

posted by 자유로운설탕
2025. 11. 15. 21:34 일본어와 중국어

그래도 왠지 언어에 대한 글을 시작한 이상 시험에 대해서는 한번 다루고 슬슬 마무리 수순으로 들어 가야겠다는 생각이 들어서, 경험 했던 일본어 JLPT 의 N2, N1 시험과 중국어의 5급, 6급 시험(실제로는 여긴 4->5->6급 순으로 보긴 했다)을 한번 살펴보고 취미로 공부하는 경우 어느 정도 공부를 해야 붙을 수 있을 지에 대해서 얘기를 해보자. 한번 붙었지만 다시 본다면 똑같이 붙을 자신은 없는 지라, 그냥 재미 삼아 참고로 들었으면 한다.

또한 시험의 스타일과 내용은 세월에 따라 변할 수도 있으니, 시험에 대한 얘기를 하면서 시험으로부터 얻을 수 있는 것을 생각해보는 관점에서 얘기해보자. 여기서는 시험 공부에 대해서 알려주는 게 아니라 개인적으로 이 정도는 공부해야 시도해 볼만하지 않을까에 대해서 포커스 해보려 한다. 

글의 방식은 각각의 시험에 대해서 공식 제공되는 샘플 시험 자료를 통해서 살펴보고, 중간 중간 시험에 대한 개인적인 생각을 풀면서 정리할까 한다.


우선 일본어 시험의 구조를 얘기해 보자. 현재의 자료는 아래와 공식 사이트에서 가져온 시험 예제 pdf 의 일부를 잘라낸 거라서, 궁금하신 분들은 아래의 사이트에서 샘플 시험 pdf 전체를 다운 받거나 관련 수험 서적, 유투브 등을 찾아보면 전체적인 맥락에서 잘 파악이 가능할 듯 싶다.

https://www.jlpt.jp/samples/sampleindex.html

일본어는 N6~N1 6단계로 구성되어 있어 숫자가 작을 수록 어렵다고 보면 되고, 크게 "듣기(청해)" 영역과, 어휘, 문법을 묶은 "언어 지식", "독해" 영역 3개로 나눠진다고 보면 된다. 어떤 외국어 시험이든 마찬가지겠지만 난이도가 높아질 수록 지문이나 대화가 길어지게 되고, 선택해야 되는 답변이나 문제의 내용도 살짝 꼬는 경향이 있다. 일본어의 경우는 일상 생활이나 회사 생활에 관한 내용을 기본 틀로 하고, 심리학이나, 사회 현상, 과학 같은 주제들을 주로 믹스하는 것 같긴 하다. 그래서 중국어 시험 보다는 좀 일상적인 드라마를 보거나 하는 게 시험에 직접적인 도움이 되는 느낌이긴 하다. 

모두 객관식 이기 때문에, 찍기의 신의 강림한다면 운 좋은 일이 생길 수도 있긴 하다(다만 우리가 학교에서 시험 볼 때를 생각해보면 찍어서 좋은 점수를 맞을 확률은 엄청 낮겠지만). 6개월에 한번만 볼 수 있기 때문에, 회사를 다니면서 보게 될 경우 회사 생활이 바빠 어영부영 하다가 어느새 시험 날이 되 버려 기회를 놓치면 다시 6개월을 기다려야 하는 막막함이 있다. 3개의 영역이 각 60점씩 배분 되어 총 180 점이며, N2 의 경우는 90점, N1 의 경우는 100점을 맞아야 합격이 된다. 영역 별 과락이 있어서 영역 중에 19점 미만인 경우가 있는 경우는 불합격이기 때문에 자신 없는 영역을 아예 포기 했다가 시험 자체도 포기하게 되는 일이 생길 수 있다. 어느 언어 시험이나 비슷한 것 같지만 문제마다의 점수 배정은 시행 기관 임의로 상대적이기 때문에, 사람들이 많이 맞춘 문제는 점수가 낮고, 많이 틀린 문제는 높은 식으로 임의로 조정되는 듯해서 일부의 운도 작용하는 것 같다. JLPT 시험은 유효 기간의 제한은 없지만 다른 실제적인 경력으로 증명할 수 없다면 회사나 학교에 따라서 특정 기간 내 봤던 점수를 요구할 수도 있다.

시험에 대한 어드밴티지는 기존 경험에 따라서 많이 다를 거 같은데, 보통 오타쿠 영역이라고 생각되는 일본 방송이라 애니 등을 아주 좋아했던 분들은 듣기에서 높은 점수를 확보하는 경우가 많다고 하고, 만화나 소설 같은 정적인 컨텐츠를 좋아했다면 아무래도 읽기나 언어 지식에 유리할 것이다(사실 이 경우는 정황 상 듣기도 어느 정도 잘하지 않을까 싶긴 하다). 유학을 다녀온 사람들은 일상 훈련을 겪어봤기 때문에 보통 듣기에는 무조건 강하고, 읽기는 본인이 현지에서 얼마나 열심히 공부 하고, 책 등을 많이 읽었느냐에 따른 편차가 있는 것 같아 보인다. 그리고 특별한 이유 없이 그냥 일본어를 공부하는 사람의 입장에서는 그냥 셋 다 비슷하게 어려운 거 같긴하고, 시험 측면에서 듣기가 제일 단 시간에 올리기 어렵고, 시험 문제 구성에 대한 운에 따른 편차도 심한 듯 하다. 블로그 등에서 몇 개월 만에 N1에 합격했다 하는 이런 사람들은 아마도 앞에 얘기한 잠재력들이 꽉 차서 넘칠 준비가 된 특별한 사람들이 아닐까 싶긴 하다. 마지막으로 세 개의 영역을 골고루 높게 잘 맞은 사람들은 정말 잘하는 사람일 테고 말이다.

시험 비용은 그렇게 비싸지도 싸지도 않은 N1, N2 기준 6만 5천원 이지만, 6개월에 한번만 볼 수 있기 때문에 할부 개념으로 따져보면 체감 상 그렇게 비싸 보이진 않는다.



그럼 우선 듣기 문제의 유형을 봐보자.

N2 같은 경우는 첫 째로 학교나, 직장, 거리 등에서 일어나는 대화를 듣고 상황을 맞추는 경우가 있다. 지문이 10개에 문제가 10개로 구성되어 있다.

[N2 듣기 1 스크립트]
[N2 듣기 1 문제]



그 다음에는 한 사람이 조금 길게 얘기를 하는 내용을 듣고 질문에 맞는 답을 하는 부분이다. 조금 특이하게도 문제지에는 답이 표시되진 않는다. 그래서 답까지 듣고 번호를 골라야 하는 경우라 첨엔 좀 부담이 된다. 지문 5개에 문제 5개로 구성되어 있다.

[N2 듣기 2 스크립트]

 

[N2 듣기 2 문제]



그 다음에는 앞의 문장을 듣고 그 다음에 응대할 문장을 찾아야 하는데, 여기도 문제지에는 답이 표시되 않는다. 지문 12개에 문제 12개로 구성되어 있다.

[N2 듣기 3 스크립트]



그 다음에는 조금 긴 대화를 듣고 1개나 2개 질문을 맞추는 문제이다 지문 3개에 문제 4개로 구성되어 있다.

[N2 듣기 4 스크립트]
[N2 듣기 4 문제]



 



다음엔 어휘 쪽을 보자

한자의 히라가나 발음을 묻는 문제가 5개 나온다. 보통 약간 우리 말로 따지면 두음법칙 같은 부분 때문에 변칙인 경우의 발음이 주로 들어가고, 히라가나와 결합된 한자의 발음을 묻는 경우도 있다. 앞의 글에서 그러한 한자들은 일부 설명했었다.

[N2 어휘 - 한자 읽기]



이후 반대로 히라가나를 보고 해당되는 한자를 찾는 문제가 5개 나온다.

[N2 어휘 - 한자 찾기]



그 다음에 괄호에 맞는 한자나 어휘를 찾는 문제가 12개 나온다.

[N2 어휘 - 맞는 한자]

 

[N2 어휘 - 맞는 어휘]



이후 밑줄 친 단어의 유의어를 찾는 문제가 5개 나오고

[N2 어휘 - 유의어]



마지막으로 해당 단어를 제일 적절하게 사용한 문장을 찾는 문제가 5개 나온다.

[N2 어휘 - 잘 사용한 문장]




 


다음엔 문법 쪽을 보자.

문장 안의 빈 칸 안에 들어갈 가장 적절한 말을 찾는 문제가 12개 나오고

[N2 문법 - 적절한 구문]



빈칸이 여러 개 나오고 특정 위치에 맞는 문장을 찾는 문장 순서를 맞추는 문제가 5개 나온다.

[N2 문법 - 해당 위치에 맞는 문장]



그 다음 긴 문장 내에서 5개의 빈칸이 있고, 각각의 빈칸에 들어가는 표현을 찾는 문제가 나온다. 1개의 지문에 문제가 5개로 구성 되어 있다. 중국어 시험에서도 비슷한 문제가 나오는데, 거긴 5개 문제가 얽혀있어서 잘못하면 다 틀리지만, 여긴 독립되어 있다.

[N2 문법 - 적당한 표현 찾기 지문]

 

[N2 문법 - 적당한 표현 찾기 선택지]

 


다음엔 독해 쪽을 보자.

중간 정도 길이의 글이나 편지 등의 문장을 하나 읽고, 답을 선택하는 문제가 5개 나온다.

[N2 독해 - 읽고 고르기]



조금 더 긴 글을 읽고 답변을 해야 한다. 3개의 지문에 9개의 문제로 구성되어 있다.

[N2 독해 - 조금 더 긴 문장 지문]
[N2 독해 - 조금 더 긴 문장 문제]



서로 다른 의견을 표하는 글을 읽은 후, 두 개의 입장을 잘 설명한 문장을 선택하는 문제이다. 1개의 지문에 2개의 문제로 구성되어 있다.

[N2 독해 - 서로 다른 의견 지문]
[N2 독해 - 서로 다른 의견 문제]



긴 문장을 읽고 답변을 해야 한다. 1개의 지문에 3개의 문제로 이루어져 있다.

[N2 독해 - 꽤 긴 문장 지문]
[N2 독해 - 꽤 긴 문장 문제]



팜플렛 같은 걸 하나 보고, 해당 내용을 이해해서 알맞은 답변을 해야 한다. 1개의 지문에 2개의 문제로 이루어져 있다.

[N2 독해 - 안내지 지문]
[N2 독해 - 안내지 답변]

 

 


대충 이렇게 보면 일본어 시험의 스타일은 알게 되지 않았나 싶다. N1 의 경우도 거의 위와 비슷한 상태로 어휘 등 난이도만 높아진다고 보면 될 것 같다. 개인적인 경험으로는 N2 같은 경우는 따로 공부를 전혀 안하고(아 물론 시험 공부를 일부러 찾아서 안 했다는 거고 학원은 회화 반등 계속 다녔었다) 평소 드라마나 노래나 게임 하던 바탕으로 시험을 보고 얼결에 붙었었는데, N1 은 시험을 보고 나서 어휘가 너무 부족함을 느껴서 N1 시험서를 찾아서 단어 정리를 한번 더 한 후에야 턱걸이로 겨우 붙게 되었었다. 뭔가 상위 시험은 공식적으로 익히기 원하는 무거운 표현들이나 한자 단어, 디테일 한 추상적인 단어들이 많기 때문에, 본인이 책을 다양하게 읽는 스타일이 아니라면 보통 해당 급수에서 원하는 단어를 한번은 훑고 시험 보러 가야 하지 않을까라는 소심한 의견을 내본다. N1 은 비슷하기도 하고 이렇게 시험 문제를 나열하다 보면 보는 사람도 피곤할 거 같아서, 일부 비교를 해볼 수 있는 부분만 선별해 봐보자.

대화를 듣는 문제가 1문제 더 나와서 12문제 나오는 거 같고, 대화의 길이가 약간 더 길고 한자어가 좀 더 많은 느낌이 있다.

[N1 듣기 1 - 지문]
[N1 듣기 1 - 문제]



답이 표시되지 않고 들어서 맞춰야 하는 문제들도 좀 아래와 같이 길어진다.

[N1 듣기 2 - 지문 1/2]
[N1 듣기 2 - 지문 2/2]

 

 


어휘나 문법 문제도 아래와 같이 약간 더 문장은 길어지고 전문적이고, 추상적인 단어들이 늘어나지 않는 가 싶다

[N1 어휘 - 단어의 올바른 쓰임]



문법도 좀 더 꼬은 듯한 표현들이 많이 추가되고, 

[N1 문법 - 알맞은 표현 고르기]
[N1 문법 - 적당한 표현 찾기]




독해도 아래와 같은 세로 읽기 문제도 부록처럼 추가되고.

[N1 독해 - 세로 읽기]



마지막에 상황 설정을 위한 자료도 아래와 같이 조금 더 복잡한 형태를 띈다(봐보지는 않았지만 중국어 7~9급 문제에 위와 같은 자료의 해석에 기반한 문제가 자주 나온다고 들었다)

[N1 독해 - 상황 자료 지문]
[N1 독해 - 상황 자료 문제]



N2, N1 간 문제의 유형이 뒤에 나올 중국어의 5급, 6급처럼 드라마틱 하게 변하지 않기 때문에, 간단히 얘기한다면 좀 더 섬세한 단어들을 많이 익히고, 한자어들을 좀 더 많이 머리 속에 담아야 하는 상황같다. 개인적으로는 일본어를 공부하다가 N2 를 따고, 이후 중국어를 한참 공부하다, 일본어 N1 단어를 공부하면서 다시 일본 한자 단어를 많이 보게 됐는데, 기존에 한자 밖에는 피할 수가 없던 중국어 공부 경험 덕분에 한자 스트레스는 사라진 후라서(한 2년 머리가 아프다 보면 사라지는 듯 싶다) 히라가나 단어 보다 한자 단어가 더 반가운 느낌이 들어 도움을 많이 되었던 것 같다(앞에서 얘기 했듯 일본어는 번체-정체라도 여전히 같은 한자는 비슷한 느낌의 한자라서 도움이 된다).


 


다음은 중국어 얘기를 해보자. 중국어는 HSK 1~9급이 있고, 1~6급은 2022년 말까지 시행된 구 시험이라고 보면 되고, 7~9급은 좀 더 상위 수준의 사용자(유럽 기준으로 CEFR C2라고 얘기하는 듯 하다)를 변별하기 위해서 신규로 만든 시험이다. 중국어로 석박사나 연구를 할 정도의 레벨의 수준을 구분하기 위해서 만들었다고 하며, 최초에는 별도의 새로운 시험으로 만들었다가 병행 단계에서 신규 시험 신청자들이 저조해서 그랬는지 7~9급만 떼어내서 기존 급수 위에 추가를 했던 걸로 기억한다. 

그래서 두 개의 급수 간 시험 스타일이 약간의 차이가 있는 거 같아서, 기존 1~6급은 듣기, 읽기, 쓰기의 3가지 영역만 있고, 매 달 볼 수 있는데에 비해서, 7~9급은 말하기와 번역 영역이 추가되고, 6개월에 한번 정도만 현재 볼 수 있고, 약간 수능 언어 영역 같은 느낌이 있다고 한다(뭐 외국어 시험의 글 내용을 어렵게 하면 수능 언어 영역이 되는거 같긴 하다). 1~6급은 개편을 시도 하려다가 현재는 여러 사정으로 멈춘 느낌이라서 아마도 정착이 되면 시간이 오래 흐르면서 원래 의도했던 대로 7~9급에 맞춰 스타일이 변경되지 않을까 싶다.

시험 비용은 사람이 판단해야 하는 쓰기 채점이 있어서 그런지 상당히 비싸게 책정된 편이라서, 컴퓨터 응시(IBT 라고 한다) 기준으로 5급은 11만원, 6급은 13만원이다(지필 응시는 만원이 싸고 결과는 컴퓨터 응시가 몇 주 더 빨리 나온다). 6급 같은 경우 한번 떨어지면 사고 싶던 물건 하나가 사라지는 느낌이라서, 비용을 다른 나라 시험과 맞춰줬음 어떨까 하는 생각은 있다. 7~9급은 번역/말하기 채점이 추가되어 인진 몰라도 더 올라가 21만 원이라서, 정말 전문적인 니즈가 생겨서 보는 사람 말고는, 호기심에 보기에는 선뜻 다가가기 힘든 비용인 느낌이 있다. 

5, 6급 기준으로 읽기, 쓰기, 듣기가 각 100점씩으로 전체 점수는 300점 만점이고 180점 이상이 되면 합격이라 일본어와 비슷하다(보통 많은 시험이 60% 정도 넘기면 합격이다. 물론 제한 시간 내에 다 풀 수 있는 정도 인지는 별개 문제 지만 말이다). 또한 일본어 시험에 있는 과락이 없어서 듣기 100점, 읽기 80점, 쓰기 0점으로도 합격이 가능하다. 그래서 중국에서 일부 살다 온 사람들은 듣기 같은 쪽에서 점수를 많이 획득해 어드벤티지를 가지는 경우도 있다고 한다. 취미 기반한 개인적인 경험으로는 6급의 경우 3개 분야의 점수가 정말 비슷비슷하게 나오고 50점 대의 벽을 만나서 점수 올리기가 많이 힘들었었다(읽기는 모의고사를 볼 땐 잘 나왔는데 시험 때만 되면 이상하게 생각했던 거와는 답이 달라서 평균 회귀가 됐었다). 

쓰기 시험은 정말 한자를 쓰는 걸 좋아한다면 직접 쓰는 지필로 보면 되겠지만, 펜을 잡아본 적이 가물가물하게 기억이 안 나는 입장이라면 컴퓨터를 이용해 앞에서 얘기한 키보드를 사용해 pinyin 으로 보는 게 확실히 유리하다. 컴퓨터나 스마트폰 입력이 많은 현실 트랜드에도 맞고 말이다. 하지만 해당 경우 실제 종이에 한자를 쓸 경우 머리 속에만 맴돌고 못 쓰게 되는 경우가 엄청 많아 질 거기 때문에 아날로그한 실 생활에서는 확실히 모자람은 있어 보인다. 또한 지필로 볼 경우는 뒷 문제를 일부 미리 보거나, 듣기 시험 시 들리는 내용을 메모를 해두거나 하는 장점이 있다는데 컴퓨터인 IBT방식으로만 시험을 본 입장에서는 경험해 보진 못했다. 그리고 쓰기가 아쉽다면 핸드폰으로 평소에 단어를 찾을 때 앞의 입력 방식 파트에서 설명했듯 필기 방식으로 찾으면 쓰는 경험을 일부 보충 할 수 있다.

일본어 시험보다 중국어 시험이 조금 더 힘들게 느껴진 부분이 2가지 있었는데, 일단 읽기 시간이 일본어 N1 시험이 어휘/문법/독해 모두 포함해서 71문제 정도에 110분이 배정되 있는데에 비해서, HSK 6급은 50문제에 50분이다. 물론 개인적으로 생각해 봤을 때 드라마나 게임 등을 많이 경험한 부분은 일본어 쪽이 조금 더 있다고는 생각하긴 하지만, 일본어 시험 같은 경우는 마지막 JLPT N1을 봤을 때 독해의 경우 애매한 문제들을 한번 더 훑어 볼 수 있을 만큼 약간 시간적 여유가 있었는데, HSK 6급 같은 경우는 4번 보는 동안 마지막 2차례만 겨우 독해 문제를 다 읽을 수 있었다. 이 쪽은 50문제에 50분이기 때문에 길거나 짧거나 상관없이 거의 1분에 1개의 문제를 풀어야 해서, 만약 해석이 아리송 해서 다시 읽으며 문제를 검토하게 되면, 시간이 모자를 가능성이 아주 높은 듯 싶다. 그래서 코로나 시기 때 시험볼 때는 마스크 쓰고 보다가 멍해진 경험도 있고, 외국인 입장에서는 단시간에 한자를 계속 머리 안에서 스캔을 하는 방식으로 빠르게 훑으면서 문제를 맞춰야 하기 때문에 쉽진 않아 보인다. 

6급 시험 서적을 보면 여러 팁들을 얘기해 주긴 하는데(맞추기 어려운 데 시간은 많이 걸리는 독해 1~10번은 뒤로 미뤘다 본다는지, 본문에서 요점을 잘 찾는 다는지), 그 팁도 사실 개인적으로는 기본적인 읽는 속도는 확보되는 상황에서 적용할 수 있지, 걷지도 못하는 상황에서 뛰는 요령을 배워봤자 해당 요령을 적용할 여유는 없는 것으로 느껴졌었다. 그래서 그냥 나중엔 정공 법으로 시도했던 기억이 있다.

애니나 노래, 연예인 등으로 입문한 사람들이 세월이 지나 나름 완성형이 되어 자기 실력이 궁금해서 한번 시험을 치뤄보는 일본어 JLPT 시험과는 달리 중국어 시험은 일반 사람들이 취미로 보는 경우는 거의 없는 느낌이다. 학원 다닐 때도 거의 유학이나 회사 생활에 필요해 공부하는 사람이 대부분 이였고, 그냥 아무 경험이 없는 사람보다는 회사, 유학이나 교환 학생 등 관련된 실제적인 목표를 가지고 시험을 보는 경우가 많아 보인다. 그래서 인터넷의 후기 등을 봐도 특히 HSK 6급 부터는 여러 사유로 현지에 가서 듣기나 읽기, 쓰기의 일부 정도는 경험을 한 사람들이 보는 게 주류인 듯 하다(그래서 주위에서도 6급 시험 계속 떨어질 때 왜 필요하지도 않은 시험을 굳이 보려 하냐는 얘기도 좀 들었었다). 요즘 주위를 보면 중국어 언어 자체는 관심이 없지만 중국/대만에서 만든 게임이나 넷플릭스 등으로 드라마 등을 보는 사람들이 많아진 느낌이라서 뭔가 멍석이 깔린 느낌이라, 시험에 대한 허들만 좀 줄이고, 당근을 좀 제공해 재밌게 접근을 하도록 유도한다면 아마 관심을 가지는 사람들이 좀 더 늘지 않을까 하는 생각이 있다.

HSK 시험은 일본어 시험과는 달리 2년의 유효기간이 있다. 그래서 강사분한테 HSK는 왜 치사하게 2년 제한을 두냐고 그랬다가, 한국어 시험인 OPIC 도 마찬가지라고 해서 깨갱한 적이 있다(그러고 보니 TOEIC 도 그렇다). 인터넷을 뒤지다 보니 요즘은 공공기관 지원 같은 경우는 2년 만료 전에 사이버국가고시센터에 등록해 두면 최대 5년까지 지원 시 유효하게 인정해 준다고 하니 그 쪽에 뜻이 있는 분들은 참고해 잘 연장해 시간과 비용을 아꼈음 한다. 운영하는 조직 입장에서는 짧게 하는게 운영 편의나 수익면에서는 날 듯한데, 국가 차원에서 하는 일에 그런 부분을 고려해 2년 제한으로 두는 시험들은 좀 야박하다는 생각이 들긴한다. 매번 시험보는 것도 부담도 되고 JLPT 처럼 그냥 기분 좋게 유효기간 없고 제출하는 기관에서 적절히 원하는 기준을 정하는게 서로 더 합리적이지 않을까 싶긴 하다.




자 그럼 시험 유형을 봐보자. 
일본어 시험 때와 마찬가지로 아래의 공식 시험 사이트에서 다운 받은 공개된 문서에서 추출해 낸 자료들이다. 
https://www.chinesetest.cn/

중국어는 일본어 시험 보다는 5급, 6급이 좀 스타일과 난이도가 명확하게 달라지는 부분들이 있어서 HSK 5급을 먼저 봐보자.

일단 듣기 문제는 짧은 대화를 듣고 현재 분위기나 주제 등을 맞추는 문제가 20개 정도 나온다. 물론 시험 보는 입장에서는 완전히 안 들릴 가능성도 높으므로 중간 중간 틀린 답을 유도하는 함정 표현들도 종종 있다.

[5급 듣기 - 지문]
[5급 듣기 - 문제]



이후 조금 긴 대화가 나오고 대화에 대해 질문을 한다. 10개의 지문에 12개의 문제로 구성되어 있다.

[5급 듣기 - 조금 긴 대화 지문]
[5급 듣기 - 조금 긴 대화 문제]



이후 여러가지 다양한 이야기가 담긴 단문에 관한 문제가 나온다. 5개의 지문에 11개의 문제로 구성되어 있다.

[5급 듣기 - 이야기 지문]
[5급 듣기 - 이야기 문제]




다음은 읽기이다. 첫 번째는 문장을 읽고 빈 칸에 맞는 단어를 선택하는 문제로 4개의 지문에 15문제로 구성되어 있다.

[5급 읽기 - 맞는 단어 조합 찾기]



그 다음에는 짧은 단문을 읽고 맞는 내용을 고르는 문제가 10문제 나온다.

[5급 읽기 - 맞는 내용 고르기]



조금 긴 글을 보면서 답을 맞추는 문제로 5개의 지문에 20문제로 구성되어 있다.

[5급 읽기 - 긴 지문 읽기]

 

 

 


그 다음엔 쓰기 이다.

문장 순서를 맞추는 문제이다. 퍼즐 맞추기 같이 순서대로 배열하며 되며, 컴퓨터(IBT) 방식 시험에서는 실제 드래그해서 순서를 맞춘다. 총 8문제가 나온다.

[5급 쓰기 - 문장 순서 맞추기]



다음 문제가 아마 처음에는 많이 당황 할 거 같은데, 예로 든 단어를 모두 넣거나, 그림을 보고 떠오르는 대로 80자 정도의 문장을 써야 하는 문제가 2개 나온다.

[5급 쓰기 - 80자 작문]






그럼 이어서 바로 6급을 보면서 5급과 비교해 보자.

일단 듣기를 보면 단문이 나오면서 맞는 문제를 찾는 부분이다. 개인적으로 여기서부터 좀 어려움이 시작되었는데 듣는 거도 듣는 거지만 들으면서 동시에 최대 12자 정도에 해당되는 4줄의 답안 목록들을 훑어 이해를 해야 되는데 과부하가 걸리게 되었었다. 읽다 보면 듣는 걸 놓치고 듣다 보면 읽는 걸 놓치면서 헤맸던 거 같다.

[6급 듣기 - 단문 스크립트]
[6급 듣기 - 단문 문제]



이후 교훈을 주는 인터뷰 형태의 대화 형식의 문제가 나오며, 3개의 인터뷰에 15개의 문제로 구성되어 있다.

[6급 듣기 - 인터뷰 스크립트]
[6급 듣기 - 인터뷰 문제]



다음으로 고사 라든가 문화재, 일화, 과학, 사회 분야 같은 부분에 대해 랜덤으로 짧은 이야기가 나온다. 6개 지문에 총 20문제로 구성되어 있다.

[6급 듣기 - 일화 스크립트]
[6급 듣기 - 일화 문제]




다음으로 읽기 인데, 맨 처음 나오는 부분이 6급 시험 때 악명이 높은 시간은 많이 들어가는데 풀기는 어려운 문제인데, 4개 중 문법, 단어의 쓰임 등이 틀린 문장을 찾는 문제이다. 개인적인 느낌으로 특정 책에서 랜덤으로 한 줄 가져와서 예문으로 해 놓은 듯한 느낌이라서 답을 보면 이해는 가는데, 문제만 보면 맥락이 전혀 없는 낯선 문장이라 해석도 잘 안되는 경우가 많았다. 10개 문제가 나와서 턱걸이만 목표일 경우는 이걸 아예 포기하고, 뒤의 장문 읽기 쪽에 좀 더 포커스해서 점수를 보충하려는 사람들도 많은 것 같긴 한데, 걍 읽는 속도를 빠르게 하려 노력하는 게 정석인 것 같긴 하다.

[6급 읽기 - 틀린 문장 찾기]



그 다음 빈칸에 들어가는 단어 고르기 인데, 이 쪽이 앞 글에서 얘기한 한국/중국어 한자의 뉘앙스 차이 때문에 고생하는 부분이기도 하다. 한국어 한자와 뉘앙스가 다른 단어가 많기 때문에 한국 사람 입장에선 아는 한자인데도 틀리는 함정이 있을 수도 있다(물론 그것보다 더 큰 이유는 모르는 한자 단어라서인 경우가 더 많긴 할 것이다). 각 순서마다 맞는 답이 중복되어 있을 수 있기 때문에 소거 법을 잘 시행해야 하고, 지필이 아닌 경우 화면을 보고 머리 속으로 잘 정리를 해야 한다. 10문항이 있다.

[6급 읽기 - 적절한 단어 조합 찾기]



다음은 맞는 문구 넣기인데, 앞의 일본어 시험의 비슷한 문제와 다르게 5개 중의 한 개를 골라 채워 넣는 거기 때문에 서로 엮어 있어, 한 두개가 아리송하기 시작하면 5개를 모두 틀릴 수도 있고, 반대로 어부지리로 모두 맞출 수도 있는 복불복 문제이기도 하다. 2개의 지문으로 10문제로 구성되어 있다. 개인적으로 봤을 때 여기서 헷갈려서 시간을 과소비하면 읽기 시험은 땡치게 된다.

[6급 읽기 - 빈칸 5개 채우기]



이후 어학 시험의 전통적인 장문 읽고 여러 문제 맞추기 이다. 5개의 지문에 20문제로 구성되어 있다.

[6급 읽기 - 장문 읽기]




다음은 쓰기이다. 쓰기는 정말 심플해서 15분 정도 A4 한 장 정도의 글을 보여 준 후, 문제가 사라진다. 이후 해당 글을 35분 안에 400자 정도로 기억을 기반으로 요약해 복원하면 된다. 

여러가지 장벽이 있을 수 있는 데, 해당 글을 읽어서 이해하지 못하면 일단 글을 요약할 수 조차 없으니 망치는 거고, 고득점(60 초과 또는 70초과)을 맞으려면 기존에 봤던 4자성어등을 잘 배치하거나, 기존 글의 좋은 표현들을 잘 기억해서 400자에 잘 담아야 한다. 주인공 이름 한자를 읽지 못해서 앞이 막막하게 된 경험도 있고(없어 보이게 요약할 때 그나 그녀로 써야만 되서), 막상 아는 부분을 짜내서 적다 보면 300자도 안 되는 경우도 많아서 감점 요인이 된다(최소 내용이 맞고 표현이 어느 정도 괜찮다는 가정에서 400자 가까이는 되어야 60점을 맞을 수 있는 듯 싶다). 

여기는 선생님이 리뷰하면서 교정을 해줘야 되는 부분도 있지만 주제가 다양하게(주로 미담인 일화나 문화적 얘기나 고사이긴 한다) 나오기 때문에 어느 정도 글로 나올 수 있는 어휘가 부족하다면(아시겠지만 표현을 알아도 읽는 거랑 쓰는 거랑 말하는 거랑은 모두 별개 이다 보니) 많이 힘들 것 같다. 다행히 쓰기 시험이라서 너무 읽기 어려운 주제나 문장으로는 가능한 안 넣는 것 같긴 하다. 개인적으로는 원래 글의 오리지널 표현들은 처음 보기도 하고 잘 기억을 못해서 많이 가져다 못 쓴 것 같고, 의미가 같은 알고 있는 표현들을 주섬주섬 꺼내 맞춰서 칸을 채운 것 같다(시험 때 이렇게 채우다 보면 좀 초라한 느낌이 든다). 그리고 생각보다 요약해 쓰다 보면 400자가 무척 길게 느껴져서 다 요약해 쓰고 나니 250자 밖에 안 되서, 더 쓸 말이 생각 안 나서 열심히 문장을 불리려고 나머지 시간을 보낸 기억도 있다. 뭐 매달 시험이 있기 때문에 시간적, 금전적 여유와 떨어졌을 때의 아쉬움을 견딜 자신이 있다면 아는 주제를 만나기 위해서 계속 보는 방법도 있긴 할 거 같다.

[6급 쓰기 - 15분 노출 원문 1/2]
[6급 쓰기 - 15분 노출 원문 2/2]





그러면 시험에 관련 없이 전체적인 관점에서 취미로 공부하는 입장에서 양 쪽 시험을 잘 통과하려면 어떻게 해야 될까? 우선 가장 중요한 부분은 읽기에 대해 충분히 익숙해 지는거라고 생각한다. 앞의 시험 문제들을 보면 듣기든 읽기든 쓰기든 해당 시험의 목적을 달성하기 위해서 일단 빠르고 정확하게 잘 읽어 이해하지 못하면 아무것도 못하게 된다. 또 JLPT N1, HSK 6급 정도가 되면 그 나라 사람 만큼은 당연히 아니겠지만, 읽기는 외국 사람 입장에서 짧은 시간 안에 나름 훑어보는 식으로 읽지 못한다면 문제를 다 풀 수 없을 만큼 시간을 여유롭게 주진 않는다. 특히 HSK 6급 볼 때는 매번 읽기 시험 보면서 시간만 조금 더 줬음 차분히 잘 풀어 붙을 수 있었을 텐데 라는 아쉬움이 있었으니까 말이다.

읽기에 익숙해 지는 방법으로는 개인적으로 특히 시간의 여유가 있다면 시험 문제만 열심히 푸는 것보다는 슈카월드 채널에서 얘기했던 자기가 좋아하는 긴 글(무협 소설이라도)을 읽어 보는 방법을 개인적으로 추천한다. 난이도가 높은 시험일 수록 결국 전문적인 주제나 관념적으로 열심히 꼬아 놓은 글들을 헤매면서 답을 찾게 될텐데, 긴 글을 많이 읽게 되면 해당 부분에 대해서 2가지 장점이 생긴다고 본다. 

첫 째는 책을 읽는다는 게 시험에 비해서는 거의 끝이 없는 글을 읽는 상황이 되기 때문에 시험에 나오는 장문이라 생각하는 문제들에 대한 스트레스가 없어져 버린다. "에게" 라는 느낌이라 할까 아무래도 상대적으로 한계가 있는 짧은 문장이라는 생각이 들 게 된다(사실 사람이 읽는 몇 권의 책도 요즘의 AI가 학습하는 빅데이터의 관점에서는 "에게"이긴 하다). 둘 째는 뭐라고 증명은 못하겠지만 책을 하나 읽을 때마다, 전체적인 글을 읽는 속도가 조금씩 빨라지게 되는 효과가 있다. 그렇게 되면 상대적으로 시험의 제한된 시간이 늘어나는 효과를 가져온다(사실 컴퓨터의 경우도 인간의 현실 시간 기준으로 봤을 때, 전자를 이용한 엄청 빠른 연산을 함으로서 인간 기준으로는 아주 짧은 시간에 엄청 빠르게 일을 하는거니까 비슷하지 않나 싶기도 하다). 꼭 책이 아니라더도 일본어 같은 경우는 책과 비슷한 용량의 텍스트 기반 방치형 게임 메뉴얼을 열심히 읽으면서 읽기가 많이 늘었던 기억이 있다. 개인적으로 우리가 외국 사람들 만큼 글을 빠르게 못 읽는 이유는, 모국어가 아니라는 부분보다는 그 사람들 만큼 글을 많이 읽지 않았던 시간적 제한과, 게으름 때문이 아닐까 라는 생각을 해본다.

듣기와 말하기, 쓰기는 항상 어느 언어의 경우나 애매하게 잘 못해서 고민하는 부분이고, 아직도 고민하는 부분이라 뭐라 좋은 방법은 얘기하긴 힘든 듯 하다. 개인적으로 드라마를 챙겨 본 게 듣기 시험에 꽤 도움이 된 거 같은데, 그 중간 다른 노력을 안 한 건 아니라서 직접적인 상관 관계가 실제 있는진 모르겠다. 잘 못하는 부분을 아는 척 얘기하면 안 될 듯 해서, 이 부분은 여러 매체에서 전문가들의 노하우들을 잘 들어보자. 

단어 암기에 대해서는 평일에는 공부할 시간을 따로 내긴 힘드니 출퇴근 하면서 부담 없이 보자는 주의였어서, 단어장에 적어서 지하철 같은데서 주로 반복해서 보는 식으로 주중에는 주로 했었었다. 우연인지는 모르겠지만 중국어나 일본어 모두 단어장(A5, 90장 자리 시트 정도)이 7개 정도 됐을 때 합격하게 됐다. 단어장에는 시험에서 나오는 단어만 정리한 건 아니고, 노래나 게임, 드라마 같은 모르는 표현들도 같이 정리했었다. 마지막 시험 전 JLPT N1 이나 HSK 6급 수험서를 살펴보며 모르는 단어나 표현을 보충해 정리했을 때 우연일 순 있지만 딱 새 단어장 1개로 정리가 됐었다. 개인적으로는 이 정도 어휘가 정리되었을 때 어느 정도 시험을 통과할 수 있게 되는구나 생각은 하고 있다. 그리고 처음 글에서 얘기했지만 각 언어를 7년~10년 정도 이렇게 낙수물 떨어지듯 천천히 공부한 거라서 세월의 지지부진한 쌓임도 아예 무시할 순 없는 것 같다.

또 많이들 하는 얘기지만 단어를 정리할 때 꼭 모르는 단어는 발견한 문장과 함께 적어 암기하자. 특히 중국어의 경우 단어만 정리하다 보면 뉘앙스를 제대로 이해 못해서 나중에 특히 회화 할 때 적절한 단어가 더 안 나오게 되는 것 같다(이 것도 중국어 공부 초반에 후회하는 부분이다). 회사에서 지원해 주거나 시간, 금전적 여유가 조금 있다면 혼자 공부하는 것도 좋겠지만 가이드를 잘 해주고 동기 부여를 해주는 좋은 강사 분을 만나 과정을 같이 하면 좀 더 좋긴 한듯 하다. 중국어는 시험의 경우 쓰기 영역 때문에 라도 조금 더 도움이 많이 되는 것 같고 말이다. AI 가 교정해 줄 수 있긴 할 것도 같은데, 그래도 데이터인 글로만(학습 소스가 영상이더라도 결국은 문자화 해 배울 듯 해서) 배운 AI 보다는 원어민의 느낌이 아직까진 다르긴 하다. 특히 나이가 어리다면 자신과 상대방의 경계심도 작아 사람을 사귀는 것이 상대적으로 쉽기 때문에, 요즘 주위에는 외국 사람들도 많기 때문에 해당 나라의 성격이 좋은 친구를 만들어 보는 것도 나쁘지 않을 것 같다(어차피 사람을 사귀는 거기 때문에 결국 자기한테 맞는 사람이 있어 보인다).

그리고 시험은 시험일 뿐이고 아무리 시험에서 가장 높은 등급(특히 턱걸이라면)에 붙는다고 해도, 이제 스스로 찾아가면서 공부할 수 있는 단계에 겨우 다다른 게 아닌가 생각한다. 단순히 시험이 목표라면 안될 것 같고 걍 평상시에 경험 하기 힘든 언어의 한 측면에 대해 압축하여 경험해 봤다고 생각하는 게 맞지 않을까 싶다. 자기 직업의 분야에서도 마찬가지겠지만 실제 그 나라 사람들은 지금의 나보다 몇 배는 더 빠르게 읽고, 말고 잘하고, 책도 많이 읽고, 무엇보다 생각을 그 언어로 하면서 언어를 계속 확장하고 있을테니까 말이다. 특히 취미라면 쫓기는 상황도 아닐테니 재밌어 보이는 방향으로 계속 나아가면 될 것 같고, 영화, 드라마, 책, 게임 같은 다른 다양한 컨텐츠 들로 부족한 퍼즐들을 열심히 채워보는 게 어떨까 싶다. 아 이건 사실 스스로 한테 하는 바램이기도 하다.

개인적으로 중국어 HSK 시험의 어휘나 주제에 대해서 아쉬운 부분은 너무 외국인 입장에서 전통적이고 교훈적이며 무겁다는 부분이다. 모든 글들이 감동이나, 문화에 대한 자부심, 해당 나라 입장에서 바람직한 깨달음을 주려는 의도가 너무 담겨 있어서 부담감이 있다. 좀 더 일상적이거나 회사 생활 같은 주제들이 많다면, 외국인 입장에서 좀 더 다양하게 표현 및 어휘를 익힐 수 있지 않을까 싶다. 제 쪽이 경험이 적어 그럴 수도 있겠지만 그런 주제 때문인지 시험 공부 과정에서 익힌 무거운 어휘 및 뉘앙스 들을 바깥 세상에서는 좀처럼 만나기가 힘든 듯 느껴진다. 모든 나라의 공식적인 언어 시험들이 나라 차원에서 주관하므로 그런 의도들을 솔찬케 넣는 편이기는 하지만, 좀 더 간접적으로 빌드업 해 내용에 녹아진다면, 좀 더 외국 사람들이 해당 시험을 보는 허들을 낮추고 의욕을 높이지 않을까 하는 소심한 생각을 해본다(전문성을 체크한다는 7~9급도 생겼으니 무거운 주제들은 그 쪽으로 좀 포커스를 옮기는 것도 어떨까 싶다). 

또 JLPT, HSK, OPIC 모두 그 나라 언어를 배우고 시험을 본다는 부분이 나름 해당 나라 입장에서는 외국 사람이 관심을 가져주는 거기 때문에 고마운 일일 수 있다고 보는데, 시험을 보는 사람들한테 뭔가 좋아하는 혜택을 주는 것도 어떨까 하는 생각도 들었다. 예를 들어 일본 같은 경우는 사람들이 좋아하는 수많은 애니메이션 관련 IP 나 약간은 너무 디테일 해 쟁글쟁글한 굿즈들이 있고, 중국도 요즘엔 인기 많은 게임이나 드라마, 전통 차 같은 혹할 만한 굿즈들이 있고, 한국도 KPOP 같은 시험을 보는 외국 사람들이 관심을 가질 좋은 주제가 있다고 본다. 어차피 국가에서 비영리적으로 관장하는 시험인데 합격한 사람들한테 소정의 문화도 자랑하고, 고마움도 표현하고, 공부도 권장하는 인기있는 기념품도 보내준다면 호감도도 늘어나고 시험도 더 활성화 되지 않을까 싶다. OPIC 한정판 BTS나 블랙핑크 굿즈를 획득하기 위해서 열심히 시험 공부하는 외국 사람을 보는 것도 개인적으론 재밌지 않을까 상상해본다.

그럼 취미로 공부하고 있고, 혹시나 시험을 보고자 했던 사람들에게 이 글이 시행착오를 피해가는 데 조금이나마 도움이 되길 빌면서 어설픈 시험 가이드 글을 마친다.

- Fin -

 

 

 

posted by 자유로운설탕
2025. 9. 28. 12:57 일본어와 중국어

7편 부터는 언어를 배우다 보면 만나는 주변 배경에 대한 잡다한 얘기 들인 편이긴 한데, 이번 글은 조금 더 개인적 취향의 글로 이해해 주었음 한다.



요즘에 노래방을 혼자 가는 거라면 코인 노래방이 적절한 듯 한데, 혼자서는 한번도 가본 적이 없는 사람들을 위해서 우선 코인 노래방 시스템부터 소개할까 한다

코인 노래방은 조금 비싼 동네는 천 원에 2곡 이고, 대학가나 젊은 사람들이 많이 사는 동네는 3곡 정도이다. 낮 시간(보통 6시 이전)에는 4곡을 주는데도 있고, 3곡이지만 100점이 나옴 1곡을 더 준 다든지, 알바생이 끝날 때 쯤 종종 서비스로 더 넣어주는 곳도 있다. 평균 잡아 3곡이라 가정 한다면 만원에 30곡이니 곡 고르고 하는 시간 포함해 4분 잡고 4*30 하면 120분이 된다. 음료 자판기에서 천 원 짜리 물 하나 사 먹고, 9천원을 노래하는데 써도 2시간 가까운 시간을 보낼 수 있다. 물론 더 비싼 동네는 천 원에 6분(이런 가격이라면 요즘 노래가 아무리 짧긴 해도 연속으로 2곡이 안 나온다. 30분에 5천원 정도) 정도도 봐서 동네마다 편차는 있다. 개인적으로 보통 2주에 한번 간다고 가정하면 한 시간에 만원이 넘어가면 뭔가 혼자 취미로 다니기에는 부담스러운 측면이 있다고 본다.

보통 시간제와 코인제를 선택할 수 있는 경우가 많은데, 어떤 동네는 강제로 시간제만 선택 가능한 경우도 있다(비싼 지역이 보통 이렇다). 간주를 점프하면서 노래만 빠르게 부르는 게 좋다면 시간제가 조금 더 좋을 수 있을 거고, 코인제는 노래하나 끝내고 잠시 물도 마시고 정비 하면서 다음 노래를 고를 여유가 있다는 장점이 있다(코인제라도 어뷰징을 막기 위해 다음 노래를 계속 안 부르고 있음 1~2분 정도 안내 후에 랜덤으로 노래를 재생한다). 개인적으로는 혼자서 2시간 정도 계속 부르게 되면 목이 중간에 못 쉬기 때문에, 간주 때나 곡을 고르면서 잠시 좀 쉬어줘야 노래 부르는 동안이나 끝나고도 목이 비교적 편안하게 유지된다고 생각해서 코인제를 추천한다.

시설 수준은 다양해서 한 두 명 겨우 들어갈 비좁은 작은 공간인 곳도 있고(이런 곳은 모니터도 가까워 눈이 아플 수도 있다), 일반 노래방 보다 살짝 작은 정도여서 친한 사이라면 2~4명은 들어가서 어느 정도 편하게 노래를 부를 수 있는 곳도 있다(아주 친하지 않은 사이라면 아무래도 뻘쭘할 수 있다). 청소도 자주 해서 깔끔하고, 화장실 같은데도 손 세정제라든지, 종이 타월 같은 걸 잘 비치 한 곳도 있으니 근처 취향에 맞는 적당한 데를 잘 찾아보자.

보통 오전 10~11시쯤 일찍 열고 12시 전에 끝나는 곳이 많다. 알바생은 낮 시간에는 보통 없고, 저녁 바쁜 시간이 되거나, 주말 같은 데만 보통 카운터에 있는 경우가 많다. 좀 오래된 곳은 천 원 지폐로 교환해서 하는 데가 있고, 요즘엔 키오스크 형태도 많아져서, 빈 방을 알아 선택하고 현금이나 카드(아마 페이도)로 결제 후, 물 하나 사서, 입구에서 마이크 커버 하나 가지고 들어가서 노래를 부르고, 시간이 다 되면 마이크를 제자리에 놓고, 먹은 음료수와 마이크 커버는 분리해서 밖의 휴지통에 버리면 된다. 낮 시간에 갈 수 있다면 아무 눈치도 안보고 노래를 하고 조용히 나올 수 있는 익명성도 주어진다.


 


다음으로 취미로 노래방을 계속 다니기 위한 팁을 얘기해 보자면 우선 좋아하는 노래가 충분히 많을 수록 좋다. 개인적으로 책이 비치 되어 있지 않은 코인 노래방도 있을 수 있고, 매번 부를 노래를 찾기는 번거로워서, 핸드폰 메모장에 좋아하는 노래들을 정리하기 시작했는데, 지금은 몇 백 곡 정도 모이게 되었다. 아무리 좋아하는 노래라도 자꾸 반복해 부르다 보면 어느 순간부터 좋다는 느낌이 무뎌질 수 있는데, 리스트 업 된 노래가 많다면 해당 부분을 어느 정도 방지할 수 있다(맛있는 음식을 질리는 선을 넘지 않도록 적절히 다양하게 돌아가며 먹는 것과 비슷하다고 할까). 동시에 계속 새로운 노래들을 찾아 레파토리를 늘리는 것도 중요한데, 개인적으로는 노래 경연 프로나 유투브 같은 데서 불러보고 싶은 노래를 듣게 되면 제목을 적어 놨다 몇 번 불러보고, 리스트에 추가할지 여부를 최종 결정한다.

나이가 좀 있는 편이라면 자기가 노래방을 한참 좋아하던 시절의 노래와, 요즘의 노래들을 섞어 구성하면 더 좋다(반대로 요즘 노래를 많이 아는 편이라면 예전 노래들도 한번 살펴보자). 생각보다 막상 불러보면 좋은 요즘 노래들이 많기도 하고, 자신과 어울리는 노래를 만나기는 생각보단 쉽진 않기 때문이다. 게다가 자기가 즐겨 부르던 시대나 장르의 노래를 부르던 방식 그대로로는, 다른 시대의 부르고 싶어진 노래들을 느낌 있게 부르기가 불가능 하다고 보기 때문에, 시대가 다른 노래들을 불러보다 보면 기존과 다른 방식으로 어떻게 불러야 할 지를 어느 시점에서 느끼게 되는 재미도 있다. 추가로 기존 노래를 부르던 방식도 뭔가 조금 더 나아지는 느낌이 들고, 생각지 못했던 다른 장르에 내 목소리가 어울리는 구나 하는 경험도 만날 수 있다.

언어 공부할 때 좋다고 해도 막상 잘 하지 않았지만 여기서 하는 한 가지 방법은, 본인의 노래를 녹음해 들어보는 거다. 예전엔 녹음기를 따로 가져가야 하는 불편한 시절도 있었지만, 지금은 스마트폰만 있음 편하게 녹음이 가능하고 음질도 훌륭하다. 아마 대부분 처음에 본인 노래를 녹음해서 들으면 생각 했던거랑 목소리가 너무 달라 이상하고, 민망함에 듣기 싫을 가능성이 높다(얼굴과 비슷하게, 자신의 민 목소리를 마냥 좋아하는 사람은 그다지 많진 않을 듯하다). 그냥 불렀을 때는 꽤 괜찮게 불렀다고 생각하던 노래가 막상 내 목소리와 너무 안 어울리거나, 고음 부분은 무리하게 짜내는 거 같아 듣기 싫고, 곡 자체의 느낌도 전혀 살리지 못한다고 느낄 수도 있다. 그런데 자꾸 반복해 들으며 복기하고, 다음에 해당 부분을 신경 써서 부르는 것을 반복 하다 보면, 자신이 노래를 부를 때 녹음되어 들릴 목소리를 대충 짐작하게 될 수 있는 설명하긴 조금 어려운 부분이 생긴다. 또한 내 목소리의 장단 점도 알게 되고 말이다. 

녹음 부분과 연관 해서 노래를 부를 때 중요한 부분이 하나 더 있다고 보는데 키 조절이다. 자기는 노래를 못 부른다고 하는 사람들을 보면 좋아하는 노래의 원 키를 그대로 놓고 부르면서 노래를 못 부른다고 생각하는 경우도 많은 것 같다. 사람마다 서로 다른 음역대의 악기를 가지고 있는 거와 같다고 보기 때문에, 노래의 가장 좋은 부분에서 가장 좋은 목소리를 낼 수 있는 자기한테 맞는 키를 찾아야 한다고 본다. 일부 남자, 여자 노래를 키를 가리지 않고 잘 부르는 사람들도 있긴 하지만, 음역대가 타고 나서 그럴테고, 잘은 모르지만 그런 사람이라도 아마 제일 좋은 소리가 나는 자기한테 제일 적절한 키가 있진 않을까 하는 생각이 든다. 그럼에도 불구하고 작곡가가 만든 원곡의 특정한 고유 음의 느낌을 꼭 살려야 한다고 하면 딱히 반박 할 말은 없긴 하다.


 


다음은 노래방 리모컨에서 실습을 위한 부분들을 간단히 설명해 보자. 보통 TJ 나 금영 노래방이 있는데, 집 근처에는 TJ 밖에 없어서 그 쪽 기준으로 설명해 본다(둘 다 거의 비슷하다). 앞 시간의 스마트폰 키보드 입력 방법을 배운다고 생각해 보고 봐보자^^

[TJ 노래방 리모컨]

 

  • "제목" 이나 "가수"를 누르면 노래 제목이나 가수 이름을 기준으로 찾을 수 있고, 그 상황에서 밑의 "한/영" 키를 누르면, 한글이나 영어로 입력 전환이 가능하다.
  • 노래마다 지정된 고유 번호가 있어서 만약 "제목"이나 "가수"를 누르지 않고 바로 번호키를 넣어서 찾을 수도 있다. 
    노래를 찾은 후 "시작" 버튼을 누름 노래가 시작된다. 
  • "마이크" 부분은 목소리가 작거나 노래방 마이크 상태의 볼륨이 작을 경우 최대 3개 정도까지 올려서 불러도 괜찮다(더 올리면 대부분 에코가 커지면서 소리가 좀 깨지는 편이다). 
  • "국가별"을 누르면 오늘의 주제인 일본, 중국 노래도 찾을 수 있게 되는데, 이건 각각 뒤에서 설명하려 한다.
  • 마지막으로 "화살표" 키는 하나 씩 찾은 곡 들을 이동하고, "쪽 이동"키는 한 페이지씩 이동한다. 이 외의 키들은 잘 사용하진 않는다.
  • 같은 노래라도 MR 이라고 붙은 곡이 있는데, 반주가 원 곡과 비슷해서 초보의 경우는 가이드 음정 같은 게 없어서 처음엔 조금 더 부르기 힘들다고 느껴질 수도 있지만, 적응하면 노래의 느낌은 그냥 midi 반주보다는 훨씬 낫기 때문에 같은 노래라면 최종으로는 MR 반주로 부르려고 해보자.

앞에서 얘기한 노래의 키를 조절하는 방법은 리모콘을 보면 "남/여 음정" 키가 있고, "b 음정 #"키가 있는데, 나랑 성별이 같은 가수가 부른 노래라면 "b 음정 #"키를 양 옆으로 조절해서 맞춰 보고(b는 낮추고, # 은 높인다), 성별이 다르다면 일단 "남/여 음정"키를 누르면 일반적인 남녀 간의 음역 차이인 4개 정도를 자동으로 올리거나 낮춰 맞춰 주는데, 그 상태에서 다시 "b 음정 #"키로 세부 조정을 해주면 된다. 아직 익숙하지 않다고 생각하는 노래는 고음도 힘들고, 멜로디도 잘 몰라 엉망이 될 가능성이 높으므로, 처음 부를 때는 익숙해 질 때까지(3~5번 정도) 1~2개 더 "b" 쪽을 눌러 음정을 낮춰서 연습을 하며 조금씩 높여서 자기의 음정 높이까지 맞추면 된다. 


아래가 핸드폰 목록에 있는 노래 중 일부인데, "+2" 는 "2개를 높인다(#)"는 의미이고, "-2"는 "2개를 낮춘다(b)"는 의미이고, "m-1" 같은 경우는 "남/여 음정"을 한 번 눌러 "여자 키를 남자 키로 바꾼 후", 이후 "1개 낮춘다(b)"는 의미이다. 음 높이를 맞추는 거는 개인적인 의견이기 때문에, 원 곡 키가 문제 없고, 그렇게 부르는 게 좋은 분들은 원 곡 키로 하여도 무방할 듯 하다.

  • 민물장어의 꿈 - 신해철 (+2)
  • 스토커 - 10CM (-2)
  • 한숨 - 이하이 (m-1)
  • Meteor - 창모 (+4)
  • 어떻게 이별까지 사랑하겠어, 널 사랑하는 거지 - AKMU (+3)
  • 숲 - 최유리 (m-1)

 


우선 일본 노래에 대해서 얘기해 보자. 좋아하는 유명한 일본 노래(JPOP)를 우리나라 노래방에서 부르는 건 비교적 수월한 일이다. 디테일한 JPOP 팬이 아니라면 잘 알려진 노래들은 대부분 한국 노래방(특히 TJ)에 있을 가능성이 높기 때문이다. 다만 마이너 한 가수나 노래, 애니 장르로 확장된다면 결국 일본 반주 기기가 있는 노래방을 찾게 되는데, 현재 국내에는 거의 "마네키네코"라는 하나의 일본 노래방 브랜드만 있는 상태이고 거기에서도 최근 기존 사용하던 DAM, Joysound(우리나라로 따짐 TJ, 금영) 같은 반주기를 없애고, 자체 브랜드 기기로 바꾸면서 지원되는 노래가 많이 줄었다고 얘기하는 것 같다. 하지만 밑에서 예를 든 노래들은 모두 있어서 보통 사람들한테는 크게 영향을 안줄 거 같다.

일본 노래를 불러 보는 것은 히라가나나 한자 읽기를 공부하는데 나쁘지 않은 방법이라고 생각한다. 제한된 시간 내에서 음을 따라가야 하는 노래의 특성 상 순발력도 어느 정도 필요하기 때문에(요즘 노래는 또 엄청 빠른 노래도 많다), 결국 자연스럽게 한자 및, 글자 읽기와 해석을 연습 하게 된다. 그래서 뭐 노래가 취향에 맞고, 할 수만 있다면 다다익선이라고 생각한다. 

한국 노래방에서 노래를 찾는 방법은 대부분 사전 식의 검색 방법을 이용해야 한다(아주 드물게 영어로 일본 노래 검색이 되는 리모컨을 보기도 한다). 위에 있던 리모콘 사진에서 "국가별"을 버튼을 누르다 보면 일본 노래 섹션이 나오게 되는데 이 후 밑의 그림 처럼 방향 키를 이용해 검색을 할수 있게 된다. 방향 키를 오른쪽, 왼쪽으로 누르면, 우리가 키보드에서 봤던 あいうえお(아이우에오, 우리로 따지면 ㄱㄴㄷㄹㅁ 이다).. 순으로 시작되는 노래가 주르륵 나오며 이후 "아래 위 방향 키"나 "쪽 넘김" 키를 이용해 곡을 찾아야 하는데, 조금 재밌게도 여기서 지난 입력 방식 글에서 얘기했던 예전 종이 사전 찾을 때의 느낌을 현재에 간접 경험할 수 있게 된다. 외국인 입장에서는 꽤 헷갈리기 때문에(아이우에오만 고려해야 하는 게 아니라 다른 글자와 조합한 순서도 찾아야 하고, 한글의 발음은 같지만 일본 글자는 틀린 경우도 있고, 한자 발음도 생각해야 하고, 영어 제목의 경우 일본어 발음도 고려해야 하기 때문에 한 번 직접 겪어 보면 좋다), 시간제 노래방에 처음 간다면 노래를 찾다가 시간이 다 흘러가게 되는 경험을 할 수도 있다.

[일본 노래 찾기- 한국 노래방 기계]

 


노래를 골랐다면 아래와 같이 한국 노래방의 경우 일본 노래를 부를 수 있게 되는데, 밑에서 일본 노래방 기기의 예를 보겠지만, 거기에는 없는 한국 발음이 한글로 적혀있기 때문에, 일본어를 몰라도 한글을 보면서 노래를 부르는 사람들도 꽤 있는 듯 하다. 뭔 그것도 나쁘진 않긴 한데, 노래란 게 멜로디, 가사, 전체적인 느낌이 있다고 보는데, 가사의 뜻을 알면서 부르면 더 느낌이 좋기 때문에 일본 노래가 좋아 한글을 보면서 부르는 분들은 이왕 취향에 맞는 김에 꼭 일본어를 배워 보길 추천한다(조금 재밌게도 비슷하게 일본 노래방 기기에서 한국 노래 찾아 부르면 한글 가사 밑에 일본어로 발음이 달려있다. 그것도 함 경험해 보면 좋다).

【한국 노래방 기계 - Pretender】


요즘은 대안으로 스마트폰에서 TJ 나 금영 노래방 앱을 다운 받아(웹 사이트도 있다) 거기서 이전 글에서 설명했던 핸드폰 키보드 입력을 이용해서 일본어로 찾아봐도 된다. 개인적으로 일본 노래나 특정 연예인, 만화, 애니메이션에 빠져 일본어를 공부한 케이스는 아니기 때문에, 학원 다닐 때 노래방에 같이 같던 사람이 불러줬던 노래나, 공유해 준 플레이리스트, 가끔 유투브에서 화제가 되서 줏은 노래들이 대부분이다. 지금도 부르기 좋다고 생각하는 노래 20곡 정도를 발매 년도 별로 정리해 봤고, 초보인 경우는 찾기가 어려울 듯 해서 메모 된 노래 번호가 있는 경우 같이 적어봤다(예로 tj27684, k43852 라면 tj 가 tj 노래방, k 가 금영 노래방 기계 번호 이다). "노래이름 - 가수"를 복사해 유투브에서 검색하면 1개의 노래 빼고는 모두 들어볼 수 있으니 들어보고 본인 취향에 맞다면 한번 코인 노래방으로 가서 불러보자. 한 쪽 노래방에만 있는 곡도 꽤 있고, *** 표시는 우리나라 노래방 기기에는 없어서, 밑에서 추가로 설명할 일본 노래방 기계가 있는 곳이나 일본 현지에서만 가능하다.

  • ENDLESS RAIN - X JAPAN (1989, tj6773, k40993)
  • カムフラージュ - 竹内まりや (1998, 타케우치 마리야, tj6697) - (カムフラージュ, 카무후라-쥬, Camouflage)
  • everything - missa (2000, tj25001, k41144)
  • 気持ちはつたわる - boa (2001, tj25137) - 気持ち(きもち, 키모치, 기분/마음) は(와, ~은) つたわる(伝わる, 츠타와루, 전해지다) - 마음은 전해진다 
  • life is - 平井堅 (2003, 히라이 켄, tj25701, k41670)
  • 雪の花 - 中島美嘉 (2003, 나카시마 미카, tj62161) - ゆき(유키, 눈) の(노, ~의) はな(하나, 꽃) - 눈의 꽃
  • ライオンハート - SMAP (2005, tj6901, k41092) - ライオン(라이온) ハート(하토) - lion heart
  • ここにいるよ - SoulJa (2007, k43009) - ここに(코코니, 여기) いるよ(있어) - (내가) 여기 있어
  • selene - orange pekoe (2008, ***) - 달의 여신
  • トイレの神様 - 植村花菜 (2010, 우에무라 카나, tj27139) - トイレ(토이레, 화장실) の(노, ~의) 神様(かみさま, 카미사마, 신, 여기서는 내용 상 여신) - 화장실의 여신
  • beginner - AKB48 (2010, tj27118, k43300)
  • 未来のムュ-ジアム - Perfume (2013, k43655) - 未来(みらい, 미라이, 미래) の(노, ~의) ムュ-ジアム(뮤-지아무, 뮤지엄) - 미래의 뮤지엄 (금영에만 있는 듯)
  • Dragon Night - 世界の終わり (2014, 세카이노 오와리, tj27684, k43852)
  • 僕が死のうと思ったのは - amazarashi(원곡 - 中島美嘉) (2016, tj28688, k44147) - 僕(ぼく, 보쿠, 내) が(가, ~가) 死のう(しのうと, 시노우토, 죽으려고) 思ったのは(おもったのは, 오못타노와, 생각 했던 것은) - 내가 죽으려고 생각했던 것은
  • ハッピーエンド - back number (2016, tj28675, k44136) - ハッピー(하피) エンド(엔도) - happy end
  • 愛を伝えたいだとか - あいみょん (2017, 아이묭, tj28907, k44310) - 愛(あい, 아이, 사랑) を(오, ~을) 伝えたい(つたえたい, 츠타에타이, 전하고 싶다) だとか(다토카, ~든가) - 사랑을 전하고 싶다던가
  • 君はロックを聴かない - あいみょん (2017, tj28948, k44439) - 君(きみ, 키미, 너) は(와, 는) (ロック, 롯쿠, rock) を(오, ~을) 聴かない(きかない, 키카나이, 듣지 않아) - 너는 록을 듣지 않아
  • 世界が終わる夜に - チャットモンチー (2017, chatmonchy, ***) - 世界(せかい, 세카이, 세계) が(가, ~가) 終わる(おわる, 오와루, 끝나다) 夜(よる, 요루, 밤) に(니, ~에) - 세계가 끝나는 밤에
  • 素直 - チャットモンチー (2017, ***) - 素直(すなお, 수나오, 솔직함/순수함) - 이 곡이 유일하게 유투브에서 밴드가 부르는 것을 들을 수 없어서 지니뮤직에서 chatmonchy로 찾으면 영문 발음인 sunao 로 들을 수 있긴 하다.
  • lemon - 米津玄師 (2018, 요네즈 켄시, tj28822, k44253)
  • 夜に駆ける - YOASOBI (2019, tj68381, k44656) - 夜(よる, 요루, 밤) に(니, ~에서/을) 駆ける(かける, 카케루, 달리다) - 밤을 달리다.
  • pretender - Official髭男dism (2019, 오피셜 히게단디즘, tj68058, k44438)
  • 踊り子 - Vaundy (2021, tj68807, k76508) - 踊り(おどり, 오도리, 춤) 子(こ, 사람/아이) - 무희/춤추는 아이
  • うっせぇわ - Ado (2022, tj68458, k75999) - 시끄러



마지막으로 일본 노래방 기기의 경우 국내에는 아까 얘기한 마네키네코가 있는데, 요즘 리모컨이 사라졌다는 얘기를 들어서 글을 쓰기 위해 한번 가 보고 싶긴 했었지만, 평일 낮에 가야지 어느 정도 합리적인 가격이 되는 특성 상 시간은 내기가 어려워서, 궁여지책으로 국내 앱스토어에서 해당 노래방 앱을 다운 받아 봤다. 실제 노래 검색이나 시작, 키 조정 등의 리모컨 기능도 핸드폰으로 진행하게 되는데 실제 연결은 못해봤지만 동영상으로 간단히 만들어 봤으니 한번 참고하길 빈다. 배운 건 언젠가 써먹을 일이 있다고 일본어 키보드 입력할 때 배웠던 걸, 여기서 유용하게 사용해 볼 수 있다.

[일본 노래방 기계 - 앱 사용하기]


대충 플로우를 보면 QR 코드를 스캔해서 현재 노래방 기기에 연결된 페이지를 띄운 후, 거기서 노래를 선택해서 부르는 구조일 것 같다. 아마 회원 가입하고 그럼 자주 부르는 노래 같은 건 즐겨찾기 하거나 해서 빠르게 선택할 수 있게도 되어있을 것이다. 일본 현지도 뭐 비슷할거 같긴 한데, 그 쪽은 10년전에 갔던 노래방이여서 얘기할 건 못 되는 것 같고, 경험 상 외국 사람일 경우는 비회원으로 진행해야 할 것이기 때문에 회화를 조금 해야 할 것 같긴 하지만, 여행 가서 그 나라 노래방에 노래 부르러 갈 정도의 정성이라면 그 정도는 대응 가능하지 않을까 싶다. 요즘은 그 나라 말을 좀 안다면 더욱 제대로 잘 쓸 수 있는 번역기들도 많고 말이다.

 

추가로 한국 노래방에서만 부르던 사람들은 일본 노래방 기계의 경우 생각 못하던 장벽을 하나 만나게 되는데, 앞에서 본 한국 노래방의 일본 노래에 달려있는 한글 읽기 발음이 없고, 한자도 쉬운 한자들은 히라가나가 안 붙어 있는 경우가 더 많다. 그래서 언어의 연습 면에서는 더 리얼한 측면이 있다. 나름 신선한 경험이라서 언어를 배우고 기회가 된다면 꼭 한번 시도해 경험해 보자.

[일본 노래방 기계 - Pretender]

[유투브 【カラオケ】Pretender / Official髭男dism 에서 캡쳐]


 


이번엔 중국 노래를 보자. 보통 조금 나이가 있는 한국 사람들이 중국 노래를 얘기하면 "첨밀밀(甜蜜蜜, tiánmìmì, 티엔미미"을 많이 얘기하는데, 개인적으로 이건 좀 오해의 측면을 가져온다고 본다. 해당 영화가 1996년에 개봉해서 주제가로 많이 알려져 그렇게 생각하겠지만, 해당 곡은 리메이크 곡이라서 처음 나왔을 때는 1979년 이다. 우리나라로 치면 심수봉의 "그때 그 사람", 산울림의 "개구장이" 같은 노래가 나오던 시대의 노래이다. 물론 옛 시절의 좋은 노래가 그 나라의 노래를 대표하지 않거나, 꼭 현재의 노래들 보다 세련되지 못하다고 할 수는 없겠지만, 아무래도 현실을 사는 우리로서는 우리나라의 노래들을 궁금해 하는 외국 사람에게 1979년의 노래를 예로 소개하는 건 세월의 간극이 너무 느껴지지 않나 싶다.

개인적으로 아쉽게도 국내 노래방에서는 좋아하는 중국 노래들을 거의 찾을 수가 없다. 중국어 기계가 있는 노래방을 찾아보면서 한번 홍대에 있는 중국 유학생들이 가는 KTV가 있어 찼아가 봤었는데, 혼자 가서 노래 부르는 것을 반기는 분위기도 아니고, 음식을 꼭 시켜야 하는 구조여서 1시간에 5~8만원 정도는 들어야 노래를 부르는 게 가능한 듯 싶어서 포기했다. 변명이라면 그래서 아무리 부르고 싶은 노래가 있더라도 쉽게 부르긴 힘든 환경이라서 새로운 노래들을 적극적으로 찾아보지 않았던 편이 였기도 하다. 

거기에 추가로 공부 초반에 노래를 열심히 외웠는데, 가사 외기와 노래 부르기에 급하다보니 한자의 발음만 외고 성조를 무시하고 안 외는 상황이 발생하기 시작했다(중국 사람들은 노래에서도 성조가 느껴진다고 하는데 외국인 입장에서는 여전히 잘 모르겠다. 다만 노래를 원곡과 비슷한 느낌으로 잘 부르면 성조를 잘 지킨다는 얘기를 듣는 것은 같다). 그러다 보니 발음은 아는데 성조를 잘 몰라서 나중에 고생을 하는 일도 생겼다.

또 중국 노래에서 사용하는 단어나 표현은 일상 대화보다는 훨씬 추상적이여서, 뉘앙스를 정확하게 이해하기 어렵고 실제 일상에서 쓰기 힘들다(물론 중국어를 원래 잘하는 사람한테는 해당 안 될 거다). 그래서 앞 에서의 일본어의 경우는 노래를 부르는 부분을 무조건 추천하는데, 중국어는 노래를 부르는 걸 좋아한다면 힘들겠지만 개인적인 실패를 거울 삼아 꼭 성조도 같이 외우라고 얘기하고 싶다. 아직도 성조를 모르는 한자가 너무 많아서 헤메고 있는 상황이고, 처음에 제대로 안 익혀 놓음 교정하기도 너무 힘든 것 같다. 다른 측면에서 중국 사람들은 우리가 생각하는 1성, 2성 같은 성조 자체를 아예 의식하진 않는 듯 싶다. 네이티브들이 다들 마찬가지겠지만 원래 당연히 그렇게 말하는 습관이 된 상태이고 강사 분한테 성조가 뭐냐고 물어보면 반대로 발음하며 따져보고 알려주곤 한다.



한국 노래방에서 중국 노래를 찾는 방법은 앞에 설명한 키보드 입력의 기초를 배웠다면 아주 간단하다. 특히 한국 노래방 기기에는 노래도 많지 않아서 찾을 때 헷갈릴 일도 별로 없다. 위의 그림에서 설명한 "국가별"을 버튼을 누르다 보면 "중국 노래"가 나오게 되고, 이후 밑의 그림과 같이 pinyin 발음의 첫 글자를 적는다. 예를 들어 他不爱我 라면 "tā bù ài wǒ" 발음의 첫 자만 순서대로 "tbaw"라고 적으면 아래와 같이 해당 되는 노래가 표시되어 나오게 된다(아마 전체 발음으로도 될 거 같긴한데 확실하겐 모르겠다). 노래의 경우는 현실의 사전 보다는 아마 목록들이 제한되어 있으니 단축 검색 법으로 쉽게 찾을 수 있다(키보드에서 저렇게 첫 영어로만 찾음 현실의 어휘에서는 경우의 수가 많아 좀 꼬일거다)

[중국 노래 찾기 - 한국 노래방 기계]



이렇게 노래를 부르게 되면 아래와 같이 화면을 볼 수 있는데, 우리가 앞의 키보드 파트에서 배웠던 pinyin 발음 기호가 보인다. 요 기호를 보면서 첨엔 모르는 단어를 잘 넘길 수 있다. 개인적으로 노래를 부르면 노래 부르기 바빠서 sh, zh 발음을 s, z 로 하는 나쁜 습관이 있다.

[한국 노래방 기계 - 他不爱我】



 개인적인 느낌으로 중국 노래들은 아직까지는 예전 우리나라 1990~2000년 초반 분위기의 노래들이 많은 듯하다. 물론 적극적으로 찾지 않아 잘 모를 수도 있고, 다른 나라의 노래 중에는 외국 사람들이 공감하기 힘든 노래 장르들도 있기 때문에 직접 찾아보길 추천한다. 아래 노래들도 모두 유투브에서 들을 수 있는 걸 확인 했기 때문에 "제목과 가수 이름"을 복사해 검색하여 한번 들어보고 맘에 든다면 노래방에서 시도해 보는 것도 어떨까 싶다. 다만 이 경우도 중국 노래방 기계에서만 있는 부를 수 있는 곡 들이 더 많은 듯 해서 개인적으론 아쉽다(*** 표시한 곡 들이 중국 노래방 기계에서만 가능하다). 일본어 보다는 발음만 안다면 노래를 찾기가 엄청 쉽긴 하지만, 혹시 몰라 금영 쪽은 중국 노래 검색이 웹으로는 안되는 듯 해서 tj 것만 있는 경우 적는다.

  • 新不了情 - 万芳 (1994, wàn fāng, 완팡, tj73530) - 新(xīn, 신, 새로운) 不了(bùliǎo, 부랴오, 끝나지 않는) 情(qíng, 칭, 정/사랑) - 끝나지 않는 새로운 사랑
  • 他不爱我 - 莫文蔚 (1997, mò wén wèi, 모웬웨이, tj80015) - 他(tā, 타, 그) 不爱(bùài, 부아이, 사랑하지 않는다) 我(wǒ, 워, 나) - 그는 나를 사랑하지 않아
  • 心在跳 - 黎明 (1998, lí míng, 리밍, ***) - 心(xīn, 신, 마음/심장) 在(zài, 짜이, ~하고 있다) 跳(tiào, 티아오, 뛰다) - 심장이 뛰고 있어
  • 我可以抱你吗 - 张惠妹 (1999, zhāng huì mèi, 장훼이메이, tj80634) - 我(wǒ, 워, 나) 可以(kěyǐ, 커이, 가능하다) 抱(bào, 바오, 안다) 你(nǐ, 니, 너/당신) 吗(ma, 마, 의문사) - 당신을 안아도 될까요?
  • 天黑黑 - 孙燕姿 (2000, sūn yàn zī, 순옌즈, tj80146) - 天(tiān, 티엔, 하늘), (黑黑, hēi hēi, 헤이헤이-사투리로 "오-"라고 읽는 듯, 어둡다) - 하늘이 어둡다
  • 勇气 - 梁静茹 (2000, liáng jìng rú, 량징루, tj80047) - 勇气(yǒngqì, 용치, 용기) - 용기
  • 至少还有你 - 林忆莲 (2000, lín yì lián, 린이리엔, tj80007) - 至少(zhìshǎo, 즈샤오, 적어도) 还有(háiyǒu, 하이요유, 아직 있다) 你(nǐ, 니, 너/당신) - 적어도 당신만은 있어
  • 流年 - 王菲 (2001, wáng fēi, 왕페이, tj80138) - 流(liú, 리우, 흐르다) 年(nián, 니엔, 년/세월) - 흐르는 세월
  • 十年 - 陈奕迅 (2003, chén yì xùn, 천이쉰, 82002) - 十年(shínián, 스니엔, 십년) - 십년
  • 陌生人 - 蔡健雅 (2003, cài jiàn yǎ, 차지엔야, ***) - 陌生人(mòshēngrén, 모셩런) - 낯선 사람
  • 带带 - 蔡依林 (2004, cài yī lín, 차이이린, ***) - 带带(dàidài, 다이다이, 데려가다) - 이끌다?
  • 防空洞 - 戴佩妮 (2004, dài pèi nī , 다이페이니, ***) - 防空洞(fángkōngdòng, 팡콩동) - 방공호
  • 童话 - 光良 (2005, guāng liáng, 광량, tj87812) - 童话(tónghuà, 통화) - 동화
  • 小情歌 - 苏打绿 (2006, sū dǎ lǜ, 수다뤼, ***) - 小(xiǎo, 샤오, 작은), 情歌(qínggē, 칭거, 사랑 노래) - 작은 사랑의 노래
  • 可惜不是你 - 梁静茹 (2005) - 可惜(kěxī, 커시, 아쉽다, tj54298) 不是(búshì, 부스, 아니다) 你(nǐ, 니, 너/당신) - 아쉽지만 당신이 아니네요
  • 我会念的 - 孙燕姿 (2007, ***) - 我(wǒ, 워, 나) 会(huì, 훼이, 가능보어), 念(niàn, 니엔, 기억하다) 的(de, 더, 조사역할) - 내 마음속으로 기억할께?
  • 无神论 - 黄立行 (2007, huáng lì xíng, 황리싱, ***) - 无神论(wúshénlùn, 우션룬) - 무신론
  • 洋葱 - 杨宗纬 (2008, yáng zōng wěi, 양종웨이, ***, 금영에는 다른 가수가 부른 버전 있음) - 洋葱(yángcōng, 양총) - 양파
  • 说谎 - 林宥嘉 (2009, lín yòu jiā, 린요우지아, ***) - 说谎(shuōhuǎng, 수어황) - 거짓말을 하다
  • 我的秘密 - 邓紫棋 (2010, dèng zǐ qí, 덩즈치, ***) - 我的(wǒde, 워더, 나의) 秘密(mìmì, 미미, 비밀) - 나의 비밀
  • 如果这就是爱情 - 張靚穎 (2010, zhāng liàng yǐng, 장링잉, tj54564) - 如果(rúguǒ, 루구어, 만약) 这(zhè, 쩌, 이것) 就是(jiùshì, 찌우스, 바로 ~이다) 爱情(àiqíng, 아이칭, 사랑) - 만약 이것이 사랑이라면
  • 小星星 - 汪苏泷 (2010, wāng sū lóng, 왕수롱, ***) - 小星星(xiǎoxīngxīng, 샤오싱싱) - 작은 별
  • 因为爱情 - 陈奕迅,王菲 (2011, ***) - 因为(yīnwèi, 인웨이, ~때문에), 爱情(àiqíng, 아이칭, 사랑) - 사랑 때문에
  • 孤独患者 - 陈奕迅 (2011, tj90661) - 孤独(gūdú, 구두, 고독한) 患者(huànzhě, 환저) - 고독한 환자
  • 我的歌声里 - 曲婉静 (2012, qǔ wǎn jìng, 취완징, tj54595) - 我的(wǒde, 워더, 나의) 歌声(gēshēng, 거셩, 노래 소리) 里(lǐ, 리, 속/안) - 나의 노래속에서
  • 慢慢喜欢你 - 莫文蔚 (2018, ***) - 慢慢(mànmàn, 만만, 천천히) 喜欢(xǐhuān, 시환, 좋아하다) 你(nǐ, 니, 너/당신) - 천천히 너를 좋아하게 됬어



금년 초에 처음으로 상하이에 몇 일 다녀왔는데 그때 기억으로는 전체적인 물가는 아직 서울 보다는 60~70%정도로 쌌었고, 노래방의 경우도 많이 있는 복합 쇼핑몰 마다 있고, 규모도 크고 낮 시간에는 할인도 있어 2시간에 만원 미만으로 불렀던 기억이 난다. 다만 중국도 핸드폰으로 모든 걸 다 하는 분위기라, 처음 가게 되면 핸드폰 인증으로 회원 가입을 하라고 하는 듯 해서, 같이 간 중국 분이 잘 얘기해서 비회원으로 등록해서 사용을 하게 됬었다. 여기를 혼자 갔었다면 그 허들을 넘는 게 가능했을까 하는 생각이 들긴 한다. 중국은 관광객 입장에서 중국 내 핸드폰 인증을 할 수 없음, 이런 서비스 이용에는 큰 허들이 있는 듯 하다(물론 강사분은 저한테 외국 여행 가서 볼 것도 많은데 노래방 가고 싶어하는 게 이상한 거라고는 한다).

일본 쪽 노래방과 비슷하게 핸드폰으로 QR을 찍어 해당 방 기계에 연결해서, 노래를 선택 했던 것으로 기억한다. 중국 노래방 반주기의 특이한 점이 하나 있다면, 우리나라처럼 미디나 MR 반주로 별도 제작하는 게 아닌 거 같고 공식 뮤직비디오 영상에서 가수 목소리만 제거 한 듯한 완전 실제 미디어 버전과 같은 반주를 사용한다(이게 한국에서는 MR 개념 이지만 뭔가 조금 더 라이브 한 느낌이다). 

문제는 이 반주가 위에 추천했던 키 조절을 하게 되면 들어간 코러스나 나래이션 들이 왜곡되고, 음이 좀 뭉개지는 느낌이 생긴다(음원 형태가 다른 건지 키 조절 처리 방식이 좋은 건지 한국의 MR 같은 경우는 자연스럽게 음이 깨지지 않고 조절된다). 그래서 노래를 좀 더 부르기 어렵거나 아예 올바른 음을 잡기 힘든 경우가 많은 것 같다. 강사 분한테 물어보니 중국 사람들은 보통 키 조절을 안하고 원 키로 부른다고 하는데, 그럼 음이 높은 가수의 노래나, 남자가 여자 노래 부르거나 하는 건 일반적인 사람은 거의 불가능 할 텐데 이상하다 싶긴 한다. 몇 번 차분히 가 볼수 있는 상황이면 뭔가 방법을 찾아 보고 싶긴 한데 아쉽긴 하다.

해당 방법을 찍으러 가볼 때가 없어서 노래를 선택하는 화면을 올리지는 못하지만, 위에서 보여줬던 일본 노래방 앱 동영상과 비슷한 느낌 이여서, 노래방 리모컨 관련 용어들만 중국어로 대칭 되는 부분을 이해하면, 이후는 비슷비슷 하다고 보면 된다. 검색은 한국 노래방 기기와 마찬가지로 pinyin 의 앞 영어 알파벳만 적어 검색하는 방식인 걸로 기억한다(그 때 이런 글을 올리게 될 줄 알았다면 스크린샷 이라도 찍어놨을 텐데 아쉽다).



중국 노래방 기기에서 부를 경우 일본 노래방 기기 때와 비슷한 추가 시련이 하나 생기는데, 아래 사진과 같이 pinyin 영어 발음이 나오지 않는다는 것이다(중국 드라마 자막에 pinyin 없는 거랑 동일한 맥락이다). 그래서 한국 노래방 기계와는 다르게 한자를 못 읽거나 못 외면 아무것도 못하게 된다. 그래서 실전 연습 하기에는 이만한 환경은 없다고 보는데, 국내에서 이런 환경을 만나긴 어렵기 때문에 아쉽긴 하다. 앞에서 얘기했듯 한국 노래방 기기에는 부르고 싶은 좋은 노래들이 많이 없어서 더 더욱 아쉽고 말이다. 

[중국 노래방 기계 - 他不爱我】

[유투브 【高清KTV伴奏】他不爱我 - 莫文蔚 Karen Mok 에서 캡쳐]



- Fin -

 

 

 

 

posted by 자유로운설탕
2025. 9. 28. 12:38 일본어와 중국어

이번에는 일본어와 중국어를 어떻게 입력할 수 있는 지에 대해서 얘기해 보려고 한다.

사실 지금 이런 언어 글을 쓰는 것과는 별로 어울리지 않는 듯한 기술적 업무를 하고 있긴 하지만, 대부분의 달라 보이는 영역 들은 생각 보단 많이 얽혀 있어서, 두 개의 언어를 입력할 수 있는 방법의 변화는 하드웨어와 소프트웨어, AI 의 발전과 밀접하게 연관이 있다고 본다(개인적으로 IT에서 얘기하는 SQL(쿼리) 이나 여러 자동화에 쓰이는 파이썬과 같은 프로그래밍 언어도 결국은 비슷한 언어에 속한다고 봐서 나중에 이 글 들을 무사히 마친다면, 언어라는 관점을 가지고 다른 시리즈로 나름 설명을 진행해 보려 하고 있다)

지금은 웹으로 이루어진 사전이 당연한 시대지만, 예전에는 종이 사전 밖에 없었고, 지금은 이름도 낯선 옥편(玉篇)이라는 이름으로 불리었다(유래는 중국 최초의 한자 사전의 이름이라고 한다). 지금은 잘 상상이 안 가겠지만 사전에서 한자를 찾는 것은 무척 노동+시간 집약적인 일이 었다. 일반적으로 우리나라 사람 입장에선 아래 3가지 방법이 있었는데 

  • 전체 획 수를 세어서 그 획 수에 해당되는 한자들을 훑어 보거나
  • 부수를 기반으로 한자를 찾거나
  • 한글 발음으로 찾아야 하는데 


발음을 모르는 한자인 경우는 앞의 2가지 방법 밖에 이용할 수 밖에 없어서, 그 야 말로 공부를 하는 건지 사전을 찾는 건지 모를 정도였다. 이건 영어 같은 다른 언어도 비슷한 상황이겠지만, 영어나 한국어는 우리가 잘 알다시피 알파벳이나 가나다 순서만 알면 (물리적인?) 인덱스가 있는 것과 비슷하기 때문에(실제 사전에도 테두리 색으로 구분되어 있다) 어느 정도 요령이 붙음 속도가 나게 된다.

그래서 그 시절에 공부하던 사람들은 공부하던 시간보다 사전 찾는 시간이 더 오래 걸리게 되는 비효율적인 상황 이였고, 그래서 그런 반복 적인 부분을 덜 하게 하는 암기나 정리를 잘 하는 것이 압도적으로 공부에 유리하고 미덕 이였던 시절이라고 생각한다. 그래서 사실 요즘의 기술 발전과, AI 같은 일반적인 논리를 벗어난 새로운 영역의 응용을 보면 참 언어 공부하기 좋은 시기이고, 우리가 일의 의미를 요즘 다시 생각해 보듯이 더 나아가 공부에 대한 의미도 많이 달라지는 시기인 것 같긴 하다.

각 나라의 말을 입력하기 위해서, 키보드에 한글 같이 글자들을 새기거나, 부수 들을 새기거나, 히라가나, 가타카나를 새기는 시도들을 많이 해왔지만, 현재 고유 글을 이용해 입력하는 방식이 주도적인 경우는 세 나라 중 아마도 한국에만 있는 것 같다. 스마트폰이나 윈도우 PC 등의 키보드를 보면 일본이나 중국은 현재 영어를 기본으로 하는 qwerty 방식으로 사용한다(직접 적인 영어 표기를 잘 안 하거나, 가타카나로 표기하는 걸 택한 두 나라가 영어 식 키보드를 현대에 쓰는 건 글로벌 시대의 선택도 영향을 줬겠지만 약간 아이러니한 느낌을 주기도 한다). 

다른 이유는 한글의 자음 모음은 24개로 키보드에 넣기 부담이 적은 것에 비해, 일본어는 46개의 기본 글자가 있고(그래서 숫자 키 영역까지 글자를 채워야 한다고 한다), 중국의 한문 입력은 부수의 숫자도 많을 뿐만 아니라 아마 앞 글에 잠시 스쳐 언급했던 우리나라의 이두 문자 처럼 어느 정도 한자의 구조를 이해하는 선행 조건이 뒷 받침 해줘야 할 것 같기도 하고, 기존의 정체를 간략화 한 간체를 추구했기 때문에 그랬을 것도 같다(그래서 정체, 간체의 갈등 같이 위키를 보면 아직도 대만은 부수 방식 키보드도 많이 쓴다고 하는 것 같기도 하다). 이러한 부분에 더해 무엇보다 한자의 표기를 하는 부분에서 영어로 발음을 기반해 입력하는 방식이 가장 컴퓨터라는 기술적 수단을 이용할 때 유리했기 때문에 그런 선택을 하지 않았을까 싶긴 하다.

예전에는 전화 선을 이용하거나, 인터넷 종량제 요금제의 비싼 부분 때문에 컴퓨터나 스마트폰 들이 지금처럼 항상 인터넷으로 연결되어 있는 환경이 아니였기 때문에, 대부분의 프로그램들은 독립적인 기계 안의 로직으로 갇혀서 돌아갔다(어찌 보면 지금의 AI 모델이란 것도 아직은 이런 비슷한 방식인 것 같아 언젠간 스스로 진화하는 상황이 되지 않을 까도 싶다). 그래서 결국 해당 키보드 신호를 효율적으로 해석하는 프로그램을 제작한 회사(그 때는 OS 및 그래도 나름 춘추전국 시대였던 한글이나 MS Word, 지금은 과거의 유물인 로터스 같은 워드프로세서 회사들)의 역량에 따라서 입력의 편리함이 결정되던 때였다고 본다(요즘 같음 어림도 없겠지만). 또한 그렇게 빠르지 못하고 메모리가 제한된 하드웨어 자원에서 입력 부분에 많은 부분을 할애는 못했을 거기 때문에, 뭔가 고급스럽게 되려면 느리고 힘들어 지는 상황이였다고 본다.

화면의 해상도도 낮고, 작은 모바일 기기는 물리적 키보드가 달린 경우가 많아서 작은 수의 키로 글자의 입력을 가능하게 하는 것도 사용성을 위한 하나의 미덕 이였다. 그래서 한글도 한참 핸드폰에서 12개의 키를 가지고 10개 정도를 이용해 입력하는 천지인이라는 입력 법을 지원 안 하면 사용을 거부하는 빠른 입력을 자랑하는 매니아 들도 있었었다(아마 지금도 여기 저기에 숨어 있을 거다). 재밌는 건 일본이나 중국 키보드도 약간 그렇게 9~12개 키를 이용한 입력 방식들이 있다(밑 에서 함 영상으로 봐보자). 

그러다 계산기 같은 독립된 형태의 전자 사전이 나오면서 한참 부모님들의 등골을 빼먹었는데, 실효성 면에서는 종이 사전을 찾는 것보다 엄청 편해지고, 검색이 빨라졌기 때문에 큰 의미가 있었다. 다만 해당 방식도 위에 얘기한 모르는 중국어, 일본어의 한자를 찾는 부분에서는 크게 도움이 안되는 상황인 건 비슷했다. 해당 부분을 해결하기 위해서 후반에 야심차게 필기 인식이 되는 전자사전이 나와서 요즘의 스위치를 사는 맘으로 큰 맘먹고 구매한 적이 있었는데, 그 엄청 답답한 필기 인식율에 스스로의 악필에 대해 원망만 했던 기억이 있다. 요즘 같이 AI 로 학습하거나 하지 않고, 100% 정직한 통계나 패턴 분석에 기반한 프로그래밍을 기반해 구현을 했었을 거기 때문에, 스스로의 글자도 악필이긴 했지만, 올바른 정자 체가 아님 아예 입력이 되지 않는 수준 이였던 것 같다(요 부분도 동영상으로 지금도 여전한 악필을 보여주며 현재의 커스텀 키보드의 뛰어난 필기 인식율을 보여주려 한다). 요즘도 일본에서는 전자사전을 여전히 잘 쓰는 거 같다고 위키에 나와있긴 하다(전자사전 모양이 궁금하다면 "카시오 ex word" 로 이미지를 찾아보면 과거부터 최근까지 모습을 볼 수 있다)

그래서 현재의 여러 온라인 사전이나 윈도우, 커스텀 키보드 들의 입력 법에 대해 당연히 이 정도는 되야 된다고 생각할지 모르겠지만, 여기까지 온 과정을 생각해보면 정말 기술이나 AI 와 같은 새로운 접근 방식에 도움을 많이 받은 것 같다. 요즘은 번역 쪽도 당연히 그렇겠지만, 기술적인 발전은 언어적 공부의 영역에도 많은 영향을 주는 듯 싶다. 여러 언어 학습 앱 들도 다들 AI를 채택했음을 표방하고 있기도 하고 말이다. 

서론을 이렇게 주절 주절 얘기한 이유는, 개인적으로 두 언어 다 처음 배웠던 키보드가 우연히 9, 12개의 버튼으로 이루어진 키보드였기 때문이다(아마 이때 다르게 배웠다면 존재조차 몰랐을 것 같다). 근데 이 방식의 키보드들도 나름 살펴봄 장점들이 있어서, 같이 예제로 보여주는 게 좋을 거 같아서 다뤄보려고 한다. 그리고 처음으로 키보드의 이해를 도울 수 있는 스크린 샷이나, 영상을 몇 개 넣어보려 한다(그 동안 링크드인은 무조건 하나의 글에 하나의 이미지만 가능한 줄 알았었는데, 일반 글 형식에는 사진이나 동영상을 여러 개 넣을 수 있다는 걸 테스트 해보고 처음 알게 되었다;).


 



그럼 일본어 키보드를 얘기해 보자. 

우선 스마트폰 쪽을 봐보자(사실 컴퓨터 쪽에 일본 키보드를 쓰는 건 관련 일을 하지 않는 이상 이런 글을 쓸 때 아님 아마 거의 쓸 일은 없을 거다) 아까 얘기했듯이 일본어는 기본 글자가 46개이다. 여기에 기본 글자에 점이 붙거나 작아져서 발음이 달라지는 촉음, 반탁음 같은 애들(예를 들면 ぱ, っ 같은 글자들)이 있지만, 우리 나라에서 shift 키를 누르고 "ㄱ"을 눌러서 "ㄲ"을 만들어 내는 식으로 비슷하게 입력 가능하다는 것을 고려하면, 12개 키 중 10개 키를 이용해서 버튼을 그냥 클릭하거나, 클릭 하며 상하좌우로 슬라이드 하는 총 5가지 가능한 행동을 고려하면, 10*5 = 50 으로 46개의 글자를 표기하고도 조금 여유가 생긴다. 이게 아이폰에서 보면 "일본어-kana" 라는 키보드다. 반대로 영문 자판 모양의 키보드가, "일본어-Romaji" 로마자 표기라고 보면 된다.

[스마트폰 KANA 키보드]

 

[스마트폰 Romaji 키보드]


저 kana 쪽은 요즘엔 많이 쓰지 않는 거 같은데, 그래도 히라가나 자체를 이용해 직접 입력 하면서 익숙해 질 수 있다는 장점도 있고, 익숙해진다면 나름 뭐 불편하지도 않다. 또한 일본어 초보라면 하나의 키가 "아이우에오", "카키쿠케코"를 동서 남북으로 나타내기 때문에 글자 익히기 순발력 연습이 확실히 된다. Romaji 는 당연한 얘기겠지만 영문 키보드 자판이기 때문에 익숙한 사람은 눈감고도 칠 수 있고, 요즘의 두 손으로 컴퓨터 자판처럼 치는 사람들은 더 더욱 효율이 좋다는 장점이 있다.

일본어 쪽은 커스텀 키보드를 써본 적은 없기 때문에, 그쪽 경험이 없어 모르지만, 이 기본 키보드 두 가지 중 하나를 선택하면 일본어를 입력하는 데에는 크게 어려움이 없다고 본다. 안드로이드 폰에서는 관련 키보드를 써본 적이 없어서 찾아보니, 일본어 키보드 설치 후에 12개 짜리 키 배열인지 qwerty 키 배열 인지를 별도로 선택하면 된다고 한다.

그럼 스마트폰에서 KANA, Romaji, 손글씨 3 종류의 키보드를 사용하여, ”夢を見る、ゆめ(유메, 꿈)を(오, 을)みる(미루, 보다), 꿈을 꾸다" 를 쓰는 영상을 봐보자. 손 글씨가 익숙해짐 잘 쓸 수 있긴 하겠지만 기본 키보드에서는 한 글자를 완전히 완성하고, 이후 다른 글자를 써야 하는 편이라서 조금 느린 편이긴 하다. 하지만 모르는 한자를 쓸 때는 많은 도움이 될 수 있다. 개인적으로는 만약 일본어랑 중국어를 같이 공부한다면 뒤의 중국어 커스텀 키보드가 이상할 정도로 일본 한자까지도 필기 인식을 잘 해주니 그 것도 고려해 보자.

[스마트폰-일본어 입력]



컴퓨터 쪽은 윈도우를 쓴다고 하면 IME 에서 추가하면 되고 여기서도 qwerty 이나 손글씨, 그리고 아마도 일본 글자 각인이 되어있는 키보드에서는 쓸 수 있는 가나 입력 모드가 있는데, 세 번째는 일본 키보드가 없어 시연을 못하니 마지막에 메뉴 위치만 보여주고 앞의 2개를 보여줄 까 한다. 이건 막상 동영상 만들다 보니 화면 상으로는 물리적 키보드를 보여줄 방법이 없어서(화상 키보드를 넣음 되긴 하는 거 같은데 영상을 크게 넣긴 좀 그래서...) 책상 위의 영어 키보드로 "yu me(夢) wo(を) mi ru(見る)" 를 누른다고 상상해 보자.

[PC - 일본어(IME) 입력]

 

 

 



다음으로 중국어 키보드를 얘기해 보자. 여기서는 번체(정체) 쪽까지 얘기함 너무 복잡해 질 듯해서, 간체만 가지고 얘기하려 한다. 아까 얘기했듯이 중국 쪽은 영어 식 발음인 pinyin(拼音) 을 이용한 쓰기가 대세이기 때문에, 굳이 다른 나라 사람이 다른 입력 방식에 관심을 둘 필요는 없을 것 같다. 중국어 키보드는 편의성 때문에 선택지가 있어 보이는데 보통 우리나라의 네이버, 다음과 비슷한 성격의 기업인 듯한 바이두(百度, baidu), 소우고우(搜狗, sougou)에서 제작한 키보드를 많이 다운 받는다. 실제 HSK 컴퓨터 방식 시험 때도 sougou 키보드가 설치되어 있다. 

다만 모바일은 설치 안 해 모르겠지만 이 sougou 키보드가 예전엔 쓰기가 담백했는데, 현재 개인용 컴퓨터 버전은 이젠 너무 스킨 사용도 강조하고, 팝업 광고도 가끔 뜨고, AI 기능 이용하라면서 메모장에 빌트 인 기능을 추가하고 언어 설정까지 강제로 바꾸고 하는 듯해서 초보자에게 추천하진 못하고, 그나마 baidu 키보드는 아직은 비교적 얌전한 듯 해서 대신 소개하려 한다. 참고로 모바일은 혹시 강요하는 게 생겼을 까 불안해서 작년 버전의 baidu 키보드를 현재 사용하고 있다(사진으로 한자를 인식하는 경우는 사진 데이터가 전송 되야 하기 때문에 키보드 전체 접근 허용을 요청 하는 것 같은데 해당 부분은 평소엔 꺼 놓고 살고 있다). 이건 국내 키보드나 해외 키보드나 아무래도 실 사용에서 사용함 익명 화 한다고 믿어 달라긴 하지만 나의 사생활 일 수 있는 정보가 간다는 데 좀 민감한 문제 같다. 요즘은 AI 보조 기능 땜에 어쩔 수 없이 허용하는 사람들도 많을 것 같긴 하지만 말이다).

그럼 한국의 천지인 처럼 9개의 키를 가지고 입력하는 경우, 영문 qwerty 배열을 사용하는 경우, 필기 체를 사용하는 경우를 baidu 키보드를 이용해서 입력하는 영상을 봐보자. 요게 하나의 키보드 안에서 내부 버튼을 이용해서 일어나는 부분이 편하긴 하고, 사용 감이나 좌우 스와이프 이동 키가 있는 게 막상 써 봄 좀 편하다.

[스마트폰 - 중국어(바이두) 입력]




다음엔 순정 키보드를 사용하여 마찬가지로 3가지 입력을 하는 걸 봐보자. 

[스마트폰 - 중국어(기본)입력]



비슷한 느낌이 들지만 실제 써보면 여러 개의 글자를 적을 때 baidu 쪽이 조금 더 자연스러운 느낌이 들긴 한다. 순정 키보드는 다양하게 써보고 싶다면 여러 개를 설치해 각각 명시적으로 이동해야 하는 부분도 조금은 불편하다. 그래서 만약 중국어 키보드 사용을 하려면 baidu 쪽을 현재로서는 개인적으로 추천한다(최신 버전의 광고나 AI 강요가 없길 바란다). 

또한 9칸 짜리 키보드를 쓰는 경우 한 칸에 여러 개의 알파벳이 있기 때문에 같은 위치에 있는 다른 언어의 조합이 될 수도 있다. 예를 들면 아까의 "wo de mimi(我的秘密)" 는 각 칸에서 어떤 알파벳을 선택해 조합 하느냐에 따라 "wo deng ni(我等你)" 로도 조합 될 수 있기 때문에, 아무래도 중국 기업 키보드가 이런 부분은 좀 더 많은 사용자의 데이터 들을 이용해 비슷하게 문장 상황에 맞게 잘 추천해 주는 것 같다. 그래서 정확한 사람들은 발음이 완전히 같기 전에는 이러한 모호성이 거의 안 생기는 qwerty 를 좀 더 선호할 수도 있다. 그리고 필기 인식도 보면 악필로 적당히 써도 찰떡 같이 알아서 잘 인식해 줘서 과거의 전자사전을 쓰며 스트레스 받던 기억을 떠올리면 상전벽해라는 느낌이 있다. 

 

이번엔 컴퓨터 용 키보드인데, 여기서는 보통 기본으로 물리적 키보드와 매칭 되는 qwerty 를 사용할 테니 특별히 커스텀 키보드를 쓸 필요는 없어 보인다. 커스텀 키보드의 장점 중 하나는 필기 인식 인데 이상하게 윈도우 중국 기본 IME에는 만든 회사가 다른지 일본어와 다르게 필기 인식이 없다. baidu 쪽을 이번에 처음으로 테스트 해봤는데, 아마도 외부 서버와 통신을 해야 할 필요가 있는데 해당 서버가 한국에서는 연결이 막힌 주소라서 필기 시 한자가 안 나오는 느낌이다. 개인적으로 모르는 한자 찾을 때는 항상 스마트폰이 옆에 있을테니 그걸로 빠르게 찾아 보는 것이 어떨까 싶긴 한다. 실제로 앞에서 얘기한 게임 같은 거를 공부 겸 해볼 때도, 옆에는 스마트폰으로 사전 사이트를 열어 놓고 찾곤 한다(안 써 봤지만 요즘은 맥북-아이폰, 갤럭시-삼성 노트북 연동도 잘되고 말이다). 아님 아까 얘기한 컴퓨터 용 sougou 키보드를 필기 용으로 쓰는 것도 방법일 것 같다. 아쉽게도 네이버 한자 사전 등 웹 기반의 필기 인식 기능 들은 대부분 아마 다른 키보드와 비교해 보면 인식 율이 조금 답답하게 느껴질 것이다. 무엇보다도 PC 환경은 뭔가 펜이나 손으로 자연스럽게 자세가 되는 입력 태블릿 등이 따로 없다면 마우스로 한자를 그리다 보면 아무래도 속이 터진다. 

그럼 여기 서는 큰 차이가 없을 것이기 때문에 윈도우 공식 키보드로 입력하는 화면을 마지막으로 봐보고 마무리를 하자.
이 부분도 물리 키보드로 "wo(我) de(的) mimi(秘密)" 라고 영문으로 넣는다고 상상하자.

[PC - 중국어(IME) 입력]

 

회사에서 그 나라 관련 일을 하지 않는 이상 요즘은 거의 스마트폰에서 검색 및 자질구레한 작업을 할 거기 때문에, 손가락으로 터치하거나 직접 그려서 쓸 수 있는 스마트폰 쪽이 휠씬 입력은 편한 것 같다. 다만 누구에게나 언젠가 올 현실적인 얘기를 하나 하자면 노안이 오는 시기들이 되면 스마트폰으로 중국어의 성조라든지 일본어의 탁음이 잘 구분이 안되고, 더 심해지면 아예 가까운 글자가 안 보이는 상황이 어쩔 수 없이 올 거기 때문에 일본어나 중국어 같이 모양이 다른 작은 점들이 있거나, 모양이 복잡한 모양의 글자의 언어는 젊었을 때 열심히 배워 두는 게 좋은 것 같긴하다(주위를 보면 빠르게는 40대 초반부터 반갑지 않게 찾아 오기 시작한다). 물론 스타일 생각 안 하고 가까운 걸 잘 볼 수 있는 편한 안경을 늘 휴대하는 것도 방법이긴 하지만 그게 생활 속에서 편하게 사용하긴 좀 힘들다. 다 초점 렌즈도 다 잘 적응하는 것 같지도 않고 말이다.

결론적으로 위에서 소개해 준 입력 방식 중 본인 맘에 드는 방식으로 선택하면 된다고 보고, 요즘은 qwerty 가 주류의 표준인 듯 하니, 두 언어 다 가능한 해당 키보드로 시작하면 좋을 것 같다. 이렇게 하는 게 나중에 컴퓨터로 입력하게 되는 상황이 오더라도 같은 방식이라 적응도 빠르게 될 것이다(개인적으로는 9키나 12키를 오래 써서 습관이 되어 편해서, 첨에 컴퓨터로 입력하는 부분이 어색하고 적응하기 힘들었었다). 거기에 더해 발음이 자신 있는 분들은 음성 인식으로 입력을 해봐도 되고(하지만 한국어를 잘해도 음성 인식으로 글자는 잘 입력 안 하니 마찬가지 일 까도 싶고), 드라마 같은데 보면 아예 이동 중이나 뉘앙스를 그대로 보내려 하는지 음성 녹음을 해서 녹음 파일을 대화로 보내는 경우도 종종 보이기도 한다. 

공부를 위해 정리할 때 발음이나 병음을 달거나 하는 하는 부분들은 요즘에는 AI 엔진에 부탁하면 그런 매칭하는 쪽은 엄청 잘해준다(물론 아직은 가끔 오타 등도 필연적으로 있긴 하다. 그래서 잘 알수록 그나마 안 속는다). 개인적으로 이 글을 쓸 때 잘 이용하고 있긴 한데, 검증을 자꾸 게을러 안 하게 되어 오타를 남기게 되는 부 작용도 생긴다.

 

마지막으로 qwerty 를 쓰다 보면 아래와 같이 막히는 부분을 만날 수 있는데, 이건 AI나 검색 엔진들한테 잘 물어보면 알려준다.
<중국어 중간점 입력>

  • Alt 키를 누른 채 오른쪽 숫자 키패드에서 + 0, 1, 8, 3을 순서대로 누르고 Alt 키를 떼면 된다(유니코드를 이용한 입력이라고 한다)

<일본어 작은 글자(っ) 입력>

  • x 뒤에 발음을 적기. ex) だった -> da xtu ta

<일본어 を 쓰기 - 현대에는 お 와 발음이 같아(o) 생기는 문제이다>

  • wo 로 쓴다(옛날엔 이 발음 이였다고 한다)

 

- Fin -

 

 

 

 

 

 

 

posted by 자유로운설탕
2025. 8. 24. 20:02 일본어와 중국어

지난번 글을 마지막으로 문법적인 비교 얘기는 더 아는 것도 이젠 없는 듯해 그만하고, 경험을 통해 참고할 수 있는 몇 가지 부분들에 대해서 얘기할 까 한다. 

이번에 얘기할 내용은 어느 언어나 다 똑같겠지만 서로 다른 영역의 컨텐츠를 접했을 때 얻을 수 있는 부분, 놓칠 수 있는 부분에 대해서 예제를 가지고 얘기를 한번 해보려 한다. 현실적인 얘기기 때문에 이전 보다 문장들이 조금 더 어려워 질 수도 있겠지만, 문장 자체를 하나하나 뜯어 보기 보다는 흐름을 보는 방향으로 글을 읽어주었음 한다^^

경험을 못해본 부분을 얘기하는 건 큰 의미가 없으므로 그나마 조금씩 맛봤다고 할 수 있는 시험, 노래, 게임, 드라마, 책 다 섯 가지를 대상으로 짧은 예를 들어보려 한다.



우선 시험은 일단 내용들이 다른 영역들에 비해서 딱딱하고 공식 적이다. 
중국어 5급, 6급 읽기 영역 문제 중에 발췌한 내용을 한번 봐보자.

[5급]

  • 朋友买了一辆新车。周末,我和他一起去试车。为了试车的性能,我们把车开得很快。

朋友(péngyǒu, 펑요우, 친구) 买了(mǎile, 마이러, 샀다) 一辆(yī liàng, 이 량, 한 대의) 新车(xīnchē, 신처, 새 차를)。

 

周末(zhōumò, 쪼우 모, 주말에),我和他(wǒ hé tā, 워 허 타, 그와 나는) 一起(yīqǐ, 이치함께) 去试车(qù shìchē, 취 스처, 시차를 하러 갔다)。

 

为了(wèile, 웨이러, ~을 위해서) 试(shì, 스, 시험하다), 车的性能(chē de xìngnéng, 처 더 싱넝, 차의 성능),我们(wǒmen, 워먼, 우리는) 把(bǎ, 바, 사역의 의미), 车(chē, 처, 차) 开得很快(kāi dé hěn kuài, 카이 더 헌 콰이, 빠르게 운전했다)。

 

  • 친구가 새 차를 샀다. 주말에 우리는 함께 시차를 하러 갔다. 차의 성능을 테스트 하기 위해서, 우리는 차를 빠르게 운전했다.

 

[6급]

  • 一个年轻人获得一份销售工作,勤勤恳恳干了大半年,却接连失败。而他的同事,个个都干出了成绩。他实在忍受不了这种痛苦。

一个(yīgè, 이거, 한) 年轻人(niánqīngrén, 니엔칭런, 젊은이가) 获得 (huòdé, 후어더, 얻었다) 一份(yī fèn, 이 펀, 양사) 销售工作(xiāoshòu gōngzuò, 샤오쇼우, 꽁쭈어, 영업직),勤勤恳恳(qínqínkěnkěn, 진진컨컨, 근면 성실하게) 干了(gànle, 깐러, 일했다) 大半年(dà bànián, 따 반니엔, 반년이 넘는 기간 동안),却(què, 췌, 하지만) 接连(jiēlián, 지에리엔, 계속해서) 失败(shībài, 스 바이, 실패하다)。

而(ér, 얼, 하지만) 他的同事(tā de tóngshì, 타 더 똥스, 그의 동료는),个个(gègè, 꺼거, 각각(하는 일마다)) 都(dōu, 또우, 모두) 干出了成绩(gànchūle chéngjì, 깐추러 청지, 성과를 내다) 。

他(tā, 타, 그는) 实在(shízài, 스 짜이, 정말로) 忍受不了(rěnshòu bùliǎo, 런쇼우 부랴오, 참을 수 없다) 这种(zhè zhǒng, 쩌 종, 이러한 종류의) 痛苦(tòngkǔ, 통 쿠, 고통)。

 

  • 한 젊은이가 영업 업무를 얻게 되었다, 반년 이상 근면 성실하게 일을 했지만, 계속해서 실패 했다. 그러나 그의 동료는 손대는 것 마다 모두 성과를 냈다. 그는 정말로 이러한 괴로움을 참을 수 없었다.




다음은 일본어 N2, N1 읽기 문제의 일부이다.

[N2]

  • 「ルール」はなぜあるのでしょうか?
    スポーツを理解するために最初に確認しておきますが、スポーツは人間が楽しむためのものです。これが出発点です。

「ルール」(루-루, 룰) は(와, 은) なぜ(나제, 왜) あるのでしょうか?(아루노데쇼우까, 있는 것일까)

スポーツ(스포-츠, 스포츠) を(오, 를) 理解(りかい, 리카이, 이해) する(스루, 하기) ために(타메니, 위하여) 最初(さいしょ, 사이쇼, 제일 처음), に(니, 으로) 確認(かくにん, 확인), して(하여) おきますが(오키마스가, 두겠지만)、スポーツ(스포-츠, 스포츠) は(와, 는) 人間(にんげん, 닌겐, 인간) が(가, 이) 楽しむ(たのしむ, 타노시무, 즐거움) ための(타메노, 위해서의) ものです(모노데스, 것 입니다)

これ(코레, 이것) が(가, 이) 出発点(しゅっぱつてん, 슛파츠텐, 출발점) です(데스, 입니다)。

 

  • 「룰」은 왜 있는 것일까요? 
    스포츠를 이해하기 위해서 우선 확인해 두겠지만, 스포츠는 인간이 즐겁기 위한 것 입니다. 이것이 출발점 입니다.

 

 


[N1]

  • 教師=話す人、生徒=聞く人という構造が知らず知らずのうちに教室空間にできあがり、そして固定化してしまうのは恐ろしいことではないかと思う。

教師(きょうし, 쿄우시, 교사)=話す(はなす, 하나스, 말하다) 人(ひと, 히토 사람)、生徒(せいと, 학생)=聞く(きく, 키쿠, 듣다) 人(ひと, 히토, 사람) という(토이우, 이라는) 構造(こうぞう, 코우죠우, 구조) が(가, 가)

知らず知らずのうちに(しらずしらずのうちに, 시라즈시라즈노우치니, 자기도 모르게) 教室空間(きょうしつくうかん, 쿄유시츠쿠우칸, 교실공간) に(니, 에) できあがり(테키아가리, 만들어지고)、

そして(소시테, 그리고) 固定化(こていか, 코테이카, 고착화) してしまうのは(시테시마우노와, 되버리는 것은) 恐ろしい(おそろしい, 오소로시이, 무서운) こと(코토, 일) ではないかと(데와나이카토, ~가 아닌가 라고) 思う(おもう, 오모우, 생각한다)。

  • 교사=말하는 사람, 학생=듣는 사람 이라는 구조가 자신도 모르게 우리의 교실 안에 형성되고, 고착화 되버리는 것은 무서운 일이 아닌가 하는 생각이 든다.



다시 봐도 참 재미없는 문장들이다.

시험은 어쨌거나 해당 시험에서 다루는 읽기, 듣기, 쓰기, 말하기 등이 특정한 수준에 도달했음을 체크하는 부분이기 때문에 다양한 어휘를 어느 수준까지 골고루 읽히는 데는 많은 도움이 된다고 본다. 특히 취미로만 공부한 사람들은 정말 편식하지 않고 높은 수준으로 까지 가진 않은 이상 만나보기가 힘든 어휘들을 시험을 통해 한번 쯤 겪어보는 것도 나쁘진 않다고 본다.

하지만 단점은 시험 공부에 대한 스트레스와 떨어졌을 때의 우울함이 있을 수 있고, 전체 과정에 거쳐 참 재미를 느끼긴 힘들다는 단점이 있다.

 



두 번째로 노래를 봐보자. 노래는 시험에 있는 문어체나 딱딱한 어휘는 거의 볼 수 없고, 거의 감정을 표현하거나, 시적인 감상을 표현하는 경우가 많다고 본다.

물론 뭐 사회의 비판이나 랩 같이 좀 직설적인 표현을 하는 경우도 있지만, 그 쪽도 표현이 마냥 직설적이기만 하면 안 되며, 결국은 서정적인 노래 가사 비슷하게 어떤 식으로든 느낌을 담아야 한다고 보기 때문에, 그렇게 많이 벗어나진 않는다고 본다.

개인적으로 언어를 배우면 제일 먼저 관심이 가지는 부분이 노래라서, 별도의 주제로 노래방에서 불러볼 만한 일본, 중국 노래를 정리해 볼까 한다.



그럼 일단 중국 노래 두 곡의 일부 가사를 봐보자. 좋아하는 노래는 각자의 취향인 부분은 이해해 주길 바란다.


[陌生人- 蔡健雅 (mò shēng rén - Cài Jiàn Yǎ, 모셩런 - 차이 지엔 야, 낯선 사람)]

  • 一朵云能载多少思念的寄托
  • 在忽然相遇街头

一朵(yī duǒ, 이 뚜어, 한조각, 한송이) 云(yún, 윈, 구름) 能(néng, 넝, 가능보어) 载(zǎi, 짜이, 실다) 多少(duōshǎo, 뚜어샤오, 얼마나), 思念(sīniàn, 스니엔, 그리움) 的(de, 더, 의) 寄托(jìtuō. 지투어, <기대 희망을> 걸다)

在(zài, 짜이, ~에서) 忽然(hūrán, 후란, 갑자기) 相遇(xiāngyù, 서로 만나다) 街头(jiētóu, 지에토우, 거리)

 

  • 한 조각의 구름은 얼마나 많은 그리움을 실어 나를 수 있을까? 
  • 갑자기 만나게 된 거리에서...

 

[慢慢喜欢你 - 莫文蔚 (mànmàn xǐhuān nǐ - mò wén wèi, 만만시환니 - 모 원 웨이, 천천히 너를 좋아하게 됬어)]

  • 刚才吻了你一下你也喜欢对吗
  • 不然怎么一直牵我的手不放


刚才(gāngcái, 깡차이, 막) 吻了(wěn le, 원러, 입 맞추다) 你(nǐ, 니, 너) 一下(yīxià, 이샤, 한번) 你也(nǐ yě, 니 예, 너도) 喜欢(xǐhuān, 씨환, 좋아하다) 对吗(duì ma, 뙤이 마, 맞지?)

不然(bùrán, 부란, 그렇지 않다면) 怎么(zěnme, 쩐머, 왜) 一直(yīzhí, 이즈, 줄곳) 牵(qiān, 치엔, 손을 잡다) 我的手(wǒ de shǒu, 워 더 쇼우, 내 손) 不放(bù fàng, 부 방, 놓치 않다)

  • 방금 내가 입 맞췄을 때 당신도 좋았던 거 맞죠? 
  • 그렇지 않다면 내 손을 왜 줄곧 잡고 있겠어요.




다음은 일본 노래 두 곡의 일부 가사를 봐보자.

[ハッピーエンド - Back number (핫피-엔도, 해피엔드)]

  • こんな時思い出す事じゃ ないとは思うんだけど
  • 一人にしないよって あれ実は嬉しかったよ

こんな(곤나, 이런) 時(とき, 도키, 때에), 思い出す(おもいだす, 오모이다스, 생각해 내다) 事(こと, 고토, 일) じゃないとは(쟈나이토와, ~은 아니라고) 思うんだけど(おもうんだけど, 생각은 하지만)

一人に(ひとりに, 혼자서) しないよって(두지않는다고 해서) あれ(아레, 그거) 実(じつ, 지츠 사실) は(와, 은) 嬉しかったよ(うれしかったよ, 우리시깟다요, 기뻤었어)

  • 이런 때에 떠올릴 만한 일은 아니라고 생각하지만
  • "혼자 두지 않을게" 라고 해서, 그 것 사실은 기뻤었어


[世界が終わる夜に - chatmonchy(チャットモンチー) (せかいがおわるよに, 세카이가 오와루 요니, 챗토몬치 - 세계가 끝나는 밤에)]

  • わたしが神様だったら こんな世界は作らなかった
  • 愛とゆう名のお守りは 結局からっぽだったんだ


わたし(와타시, 내) が(가, 가) 神様(かみさま, 카미사마, 신) だったら(닷따라, 이였다면), こんな(콘나, 이러한) 世界(せかい, 세카이, 세상) は(와, 은) 作らなかった(つくらなかった, 쯔쿠라나깟다, 만들지 않았어)

愛(あい, 아이, 사랑) とゆう(토유우, 라고 하는), 名の(なの, 이름의), お守りは(おまもりは, 오마모리와, 부적은) 結局(けっきょく, 결국) からっぽ(카랏뽀, 텅 비어있음) だったんだ(~였었다)

  • 만약에 내가 신이었다면 이 따위 세상은 만들지 않았어
  • 사랑이라고 부르는 이름의 부적은 결국 텅 비어있었어

 

노래는 거의 시적인 영역에 가깝다 보고, 언어가 서로 완전하게 표현이 대칭이 안 되는 일도 있기 때문에 해석도 좀 가지가지인 편이다. 인터넷의 외국 노래를 번역한 내용 들을 보면 의미는 맞지만 느낌이 밋밋한 경우도 있고, 해석이 정확하진 않지만 좀 더 느낌을 잘 나타내는 경우도 있는 듯 해서 케바케 인 거 같다. 한국말을 잘해도 노래 가사를 쓰는 건 또 다른 문제니 그러려니 하자.

좋아하는 음악이나 가수가 있다면 여러 노래를 즐겨보는 동기 부여도 되고, 무엇보다 노래방에 가서 부른다면 가장 활동적인 스터디 활동이 되기도 하고 스트레스나 정서 면에서도 나쁘진 않아 보인다. 요즘은 가끔 엉뚱하지만 미묘한 표현들의 해석이 막힐 때 의견을 물어 볼 AI 엔진도 있어 더 좋은 것 같다. 다만 최초에 너무 모르는 단어가 많은 시기에는 들이는 시간에 비해 아웃풋이 적을 수 있고, 표현들이 은유 적이라 일상 대화와는 조금은 거리가 멀다고 생각한다. 

 



세 번째는 게임을 보자. 게임은 보통 여러 종류의 판타지와 많이 결합되기 때문에, 앞에 얘기한 영역들의 표현에 더해 여러 현실에서 잘 만나기 힘든 다양한 표현들을 체험하기 쉽다.

비속어 같은 표현도 종종 만날 수 있고 구어체 표현도 많다. 장르에 따라서 얼마든지 특정 분야의 사실적인 어휘들과 표현들을 익힐 수 있다는 장점이 있고, 무엇보다 하면서 재밌다는 장점이 있다. 

단 이 쪽도 어느 정도 읽지 못하는 단계에서 처음부터 시작 하면, 사전 찾아 보다가 재미가 반감될 위험이 있다. 하지만 요즘은 인터넷 사전이나 키보드 등의 도구가 워낙 좋고, AI님도 도와주고 해서 취향만 맞는 다면 공부를 하면서도 동기부여도 되고 재미 또한 실시간으로 느낄 수 있는 좋은 분야라 생각한다.

 


[대만 모바일 게임인 소드 오브 콘발라리아 라는 게임의 대사이다(SRPG 좋아하면 괜찮다)]

  • 但在战术博弈里,调用全部兵力与我们真面对冲,无比是上策。

但(dàn, 딴, 하지만) 在(zài, 짜이, ~에서) 战术(zhànshù, 짠슈, 전술) 博弈(bóyì, 보이, 게임) 里(lǐ, 리, 안),

调用(diàoyòng, 띠아오용, 동원하다) 全部(quánbù, 췐부, 전) 兵力(bīnglì, 빙리, 병력) 与(yǔ, 위, ~와) 我们(wǒmen, 워먼, 우리) 真面对冲(zhēn miànduì chōng, 쩐 미엔뛔이 총, 정면충돌하다),

无比(wúbǐ, 우삐, 비할바 없다) 是(shì, 스, ~이다) 上策(shàng cè, 샹처, 상책)。

  • 하지만 전술 게임에서, 전 병력을 동원해 우리와 정면 충돌하는 것은 아주 좋은 상책이지.

 

 

  • 咳咳,该死的咳嗽,偏偏是这个时候·····

咳咳(kèké, 커커, 콜록콜록),该死(gāisǐ, 가이스, 젠장할/빌어먹을) 的(de, 더, ~의) 咳嗽(késou, 커소우, 기침),偏偏(piānpiān, 피엔피엔, 하필이면) 是(shì, 스, ~이다) 这个时候(zhège shíhòu, 쩌거 스호, 이 때)·····

  • 콜록콜록, 빌어먹을 기침, 하필이면 이럴 때에......



[일본 게임인 잔다르크라는 게임의 대사이다]

  • あんなケ違いの奴相手に見事なもんだったぜ。

あんな(안나, 저렇게나) ケ違い(ケちがい, 케치가이, 대단한) の(노, ~의) 奴(やつ, 야츠, 놈) 相手に(あいてに, 아이테니, 상대로) 見事な(みごとな, 미고토나, 훌륭한) もんだったぜ(몬닷다제, 것 이였어)。

  • 저렇게 대단한 녀석을 상대로 아주 훌륭하던데..


  • なかなかじゃねーか?

なかなか(나카나카, 꽤) じゃねーか(쟈네-카, 하지 않은가)?

  • 꽤 잘 하는데?


위의 표현들은 사실 드라마나 영화와는 약간 결이 다른 표현이라고 보지만, 요즘은 장르도 워낙 세분화 되어 다양하고 만화가 드라마나 영화가 되는 경우도 많으니, 그런 경우 예외도 있을 것은 같다.

개인적으로 게임은 뭔가 막 섞어 놓은 말 장난들을 만나는 듯한 느낌이 있다. 현실이나 드라마에서 나온다면 오글거리겠지만 게임이라서 용서가 되는 그런 표현들 말이다. 취향이 맞는다면 SRPG 같은 대사가 많은 게임을 일부러 하면 좋고, 요즘의 게임들은 가챠 뽑기를 위한 캐릭터 서사를 만드느라 대사도 많은 편이다. 외국 버전 게임을 하게 되면 공지나 공략 등도 게임을 하기 위해 궁금해서 막 찾아보게 되는 동기 부여 측면도 있다(개인적으로 텍스트 위주로 이루어진 방치형 게임 공략을 보다가 카다카나가 많이 늘었었다).

다만 아쉽게도 현재는 중국 내수 환경의 게임은 중국 핸드폰이 없으면 플레이조차 할 수 없기 때문에(청소년 시간 제한 때문일지는 모르겠지만 시작할 때 본인 인증을 해야해서 외국인 입장에서는 당황스럽다), 대안으로 PC 나 스팀 버전의 해외 판으로 플레이 하면 언어나 성우 선택이 보통 일본어/중국어가 다 된다. 뭐 원한다면 그 외의 다른 다국어 들도 풍성하게 있는 경우가 많다.

처음에 글을 더듬더듬 읽을 때라 엄두도 안 난다면, 보통 요즘은 여러 나라에 동시나 몇 개월 간격으로 다국적으로 게임 론치를 하기 때문에 같은 게임의 한국 버전과 다른 나라 버전을 동시에 깔아서 한글과 비교하면서 하는 방법도 괜찮다고 본다(덤으로 미래시라는 재밌는 경험도 하게 된다). 다만 요즘은 거의 과금 모델이 가챠라서 가챠에 빠지진 않도록 조심은 하자.


 


 

다음으로 드라마나 영화는 워낙 좋아하는 사람이 많지만 순식간에 대사가 흘러가기 때문에 난이도는 꽤 높다고 생각한다. 처음에 중국 드라마나 일본 드라마를 보게 되면 대사가 자막에 비해 정말 순식간에 지나간다. 특히 중국어의 경우 좀 더 그렇게 느껴진다. 

이쪽은 장르도 다양하고 현실의 많은 모습을 모방하거나 창조하기 때문에 소화할 수만 있다면 가장 현실적인 교재라고 생각한다. 다만 드라마나 영화를 좋아하는 사람들은 볼 컨텐츠 들이 너무 많기 때문에 굳이 스트레스를 받으며 이렇게 봐야 하냐는 생각이 들 수도 있어 보인다.



중국 드라마의 일부 대사를 봐보자

[三十而已 (sānshí éryǐ, 싼스 얼이, 겨우 삼십)]

  • 现在的世道,都是多一事不如少一事。

现在(Xiànzài, 시엔짜이, 현재) 的(de, 더, ~의) 世道(shìdào, 스따오, 세상의 상황),都是(dōu shì, 도우스, 모두) 多一事(duō yīshì, 뚜어 이스, 일이 있는 것) 不如(bùrú, 부루, ~만 하지 못하다) 少一事(shǎo yīshì, 샤오 이스, 일이 적은 것)。

  • 요즘의 세상은 모두들 일이 많은 것보다 적은 것이 낫다고 생각해(괜히 남의 복잡한 사정에 얽히거나 관심 같지 않고, 조용히 지내는 것이 가장 좋다고 생각한다는 뜻).

 

  • 可是去了运营组,我这三个月得累吐血了。

可是(kěshì, 커스, 하지만) 去了(qùle, 취러, 간다면) 运营组(yùnyíngzǔ, 윈용주, 운영팀),我(wǒ, 워, 나) 这(zhè, 쩌, 이) 三个月(sāngeyuè, 싼거웨, 3개월) 得(děi, 데이, ~할 것이다) 累吐血(lèitǔxiě, 피를 토할만큼 피곤하다) 了(le, 러, 변화의 의미)

  • 하지만 운영팀에 간다면, (나는) 아마 첫 3개월은 너무 힘들거예요.




다음 일본 드라마의 일부 대사를 봐보자

[ぼくは明日、昨日のきみとデートする (ぼくはあした、きのうのきみとデートする, 나는 내일 어제의 너와 데이트를 한다)]

  • 一目ぼれをした。

一目ぼれ(ひとめぼれ, 히토메보레, 첫눈에 반함) を(を, 오, 을) した(시타, 했다)。

  • 첫 눈에 반해버렸다.

 

 

  • 本当のこと言っていい?私ずっとあなたのことみてたんだよ

本当(本当, 혼토, 정말/진심) の(노, 의) こと(고토, 것) 言って(いって, 잇떼, 말해도) いい(이이, 좋아)?私(わたし, 와따시, 나) ずっと(즛또, 계속) あなたのこと(아나타노고토, 당신) みてたんだよ(미테딴다요, 보고 있었어)

  • 솔직하게 말해도 되? 나 계속 너를 보고 있었었어.


드라마는 좋아하는 드라마의 자막을 특정해서 구하기가 어렵다는 단점이 있지만, 요즘의 넷플릭스 드라마들은 자막이 기본으로 내장되어 있고, 완벽하진 않지만 Whisper AI 같은 자막을 추출해 주는 무료 툴 들도 많아서 상황에 따라 적절히 잘 이용해 보면 어떨까 싶다. 물론 제일 좋은 건 막힐 때 부담 없이 마음 편하게 물어볼 수 있는 지인이 있는 거라고 생각하긴 한다.


 



마지막으로 책은 드라마와 비슷하지만 글자를 기반으로만 소화를 해야 한다는 단점이 있다(그래도 화면을 오래 보는 것 보다는 눈이 편안해, 머리를 쉬게 해주는 장점은 있다고 할까?). 

장르도 다양하고 전공 서적 까지 범위가 다양하기 때문에 가장 한계가 없지만, 나의 어휘에 기반해 상상력과 이성 및 감성에 의지해서 진행 되는 정면 승부의 측면이 있다.



프랑스어를 몰라서 어린 왕자의 대사를 원문으로 시작 못하는 게 아쉽긴 하지만, 개인적으로 같은 계열이라 그런지 가장 표현이 괜찮다고 생각하는 영어에서 시작해 보자. 물론 일본어의 미묘한 표현과 중국어 한자의 말랑말랑 함을 아직 이해 못해서 그렇게 느끼는 걸 수도 있다.

 

[어린왕자 - 생텍쥐페리]

 

  • "Goodbye," said the fox. "And now here is my secret, a very simple secret: It is only with the heart that one can see rightly; what is essential is invisible to the eye."
    "What is essential is invisible to the eye," the little prince repeated, so that he would be sure to remember.
  • "안녕" 여우가 말했다. "그리고 이제 이것이 나의 비밀이야. 아주 간단한 비밀이지. 마음으로 봐야만 올바르게 볼 수 있어. 중요한 것은 눈에는 보이지 않거든"
    "중요한 것은 눈에 보이지 않아" 어린 왕자는 기억에서 잃어버리지 않도록 반복했다.




중국어 번역 본의 같은 구절을 보자. 读客三个卷经典文库 출판사에서 나온 책이다. 번역에 다시 한글 번역을 다니까 좀 이상하긴 하다 --;

  • “再见。” 狐狸说,“我要告诉你的秘密很简单:只有用心,才能看清。本质的东四。肉眼是看不见的。"
    “本质的东四,肉眼是看不见的。“ 小王子重复了一遍,为了记住这句话。

“再见(zàijiàn, 짜이찌엔, 작별인사"。” 狐狸(húlí, 후리, 여우) 说(shuō, 수어, 말하다),

“我(wǒ, 워, 나) 要(yào, 야오, 의지의 느낌) 告诉(gàosù, 까오수, 알려주다) 你(nǐ, 니, 너) 的(de, 더, ~의) 秘密(mìmì, 미미,비밀) 很(hěn, 헌, 매우) 简单(jiǎndān, 지엔단, 간단하다):

只有(zhǐyǒu, 즈요우, ~해야만) 用心(yòngxīn, 용신, 마음을 쓰다),才(cái, 차이, 비로소) 能(néng, 넝, 가능보어) 看清(kàn qīng, 칸 칭, 분명히 보다)。本质(běnzhì, 뻔즈, 본질) 的(de, 더, ~의) 东西(dōngxi, 똥시, 물건/것)。肉眼(ròuyǎn, 로우옌, 맨 눈) 是(shì, 스, 강조문법) 看不见(kàn bùjiàn, 칸 부찌엔, 볼 수 없다) 的(de, 더, 강조문법)。"

"本质(běnzhì, 뻔즈, 본질) 的(de, 더, ~의) 东西(dōngxi, 똥시, 물건/것)。肉眼(ròuyǎn, 로우옌, 맨 눈) 是(shì, 스, 강조문법) 看不见(kàn bùjiàn, 칸 부찌엔, 볼 수 없다) 的(de, 더, 강조문법)。" 小王子(xiǎowángzi, 샤오왕즈, 어린왕자) 重复(chóngfù, 쫑푸, 반복하다) 了(le, 러, 완료?) 一遍(yībiàn, 이삐엔, 한번),为了(wèile, 웨이러, ~을 위하여) 记住(jìzhù, 지주, 기억하다) 这句话(zhè jù huà, 쩌 쥐 화, 이 말)。

  • "잘 가", 여우가 말했다, "나는 너에게 매우 간단한 비밀을 알려주고 싶어: 마음을 써야만 비로서 분명하게 볼 수 있어. 본질 적인 것은 맨 눈으로는 볼 수 없거든" 
    "본질적인 것은, 맨 눈으로는 볼 수 없어" 어린 왕자는 이 말을 기억하기 위해서 계속 반복했다. 




다음은 일본어 번역 본을 보자. 岩波少年文庫 2010 출판사에서 나온 책이다.

  • 「さよなら」と、キツネがいいました。「さっきの秘密をいおうかね。なに、なんでもないことだよ。心で見なくちゃ、ものごとはよく見えないってことさ。かんじんなことは、目に見えないんだよ」
    「かんじんなことは、目に見えない」と、王子さまは、忘れないようにくりかえしました。

「さよなら(사요나라, 안녕)」と(토, ~라고)、キツネ(키츠네, 여우) が(가, ~가) いいました(이이마시다, 말했습니다)。「さっき(삿키, 아까) の(노 ~의) 秘密(ひみつ, 히미츠) を(오, 를) いおうかね(이오우카네, 말할까 해)。なに(나니, 뭐)、なんでもない(난데모나이, 아무것도 아닌) こと(고토, 것) だよ(다요, ~이야)。心(こころ, 코코로, 마음) で(데, ~으로) 見なくちゃ(みなくちゃ、미나쿠챠, 보지 않으면)、ものごと(모노고토, 본질) は(와, 은) よく(요쿠, 잘) 見えないって(みえないって, 미에나잇데. 보이지 않는) ことさ(고토사, 것이야)。かんじんな(칸진나, 중요한) こと(고토, 것) は(와, 은)、目(め, 메, 눈) に(니, ~에는) 見えないんだよ(みえないんだよ, 미에나잉다요, 보이지 않는다고)」고

「かんじんな(칸진나, 중요한) こと(고토, 것) は(와, 은)、目(め, 메, 눈) に(니, ~에는) 見えない(みえない, 미에나이, 보이지 않아)と(토, ~라고)、王子さま(おうじさま, 오-지 사마, 어린왕자) は(와, 는)、忘れない(わすれない, 와쓰레나이, 잊지 않다) ように(요우니, ~하도록) くりかえしました(쿠리카에시마시다, 반복했습니다)。

  • "잘 가"라고 여우가 말했습니다. 아까의 비밀을 말할까 해. 뭐, 별거 아니야. 마음으로 보지 않는다면, 본질은 잘 눈에 보이지 않는 다는 거지. 소중한 것은 눈에는 보이지 않는다고"
    "소중한 것은 눈에는 보이지 않는다", 어린왕자는, 잊어버리지 않기 위해서 반복했다. 


공감했을진 모르겠지만, 결론은 골고루 섞어 경험하면 된다는 얘기를 하고 싶다. 뭐 두 언어 모두 그다지 잘하지도 못하는 처지에 이런 얘기를 하는 건 좀 민망하긴 하지만, 각각의 장르는 서로 다른 어휘와 표현, 특유의 재미 들을 가지고 있다. 많이 들 얘기하지만 결국 언어 공부는 (좋아하든 싫어하든) 그 나라의 문화와 사람을 이해하는 과정이라는 측면도 있기 때문에 다양한 부분을 활용하며 경험하는 것이 좋다고 본다.

다만 현실에서의 우리는 소모할 수 있는 시간이 제한되어 있고, 보통 한 번에 2~3가지 정도의 활동만 꾸준히 관심을 가지고 할 수 있기 때문에, 해당 부분도 항상 생각하면서 선택해 보자. 생각보다 시간은 빠르게 흘러가니까 말이다.

- Fin -

 

 

 

posted by 자유로운설탕
2025. 8. 10. 16:26 일본어와 중국어

첫 번째 글에서 일부는 언급된 얘기 이기는 하지만, 두 나라 언어 속의 한자와 우리나라 한자와의 차이 부분에 대해서 조금 더 얘기를 하는 것도 의미가 있을 것 같다.


우선 일본어는 대부분의 한자 단어에서 특별히 우리나라 개별 한자의 뉘앙스나 한자 단어의 의미 상에서 차이를 느끼기가 힘든 것 같다. 또 한참 일본어 공부를 할 때에는 중국어를 배우기 전 이였기 때문에 그냥 한자가 너무 많아서 힘들다고 느끼면서 별 생각 없이 받아들였던 것 같다. 그래서 이 글을 정리하면서 이런 부분들이 있구나 하는 걸 알게 된 부분들도 있다.

그렇게 많진 않은 것 않지만, 일본어의 한자가 우리가 쓰는 한자와 다른 의미로 쓰이는 예제들을 몇 개 봐보자.

  • 大丈夫(だいじょうぶ, 다이 죠 부) - 한국어로는 "대장부"라는 의미지만 일본어로는 "괜찮아"의 의미이다. 저 "대장부"란 의미가 엄청 호탕하고 시원시원한 남자를 의미 했었으니까, 그 의미에서 "나는 외-내적으로 튼튼해서 문제 없어"의 의미가 되지 않았을까 싶다.
  • 愛人(あいじん, 아이 진) - 한국에서는 "애인"이라고 쓰지만, 일본에서는 부적절한 관계의 상대를 지칭한다고 한다. 한국과 같은 좋은 의미로 쓰려면 연인의 한자어로 恋人(こいびと, 코이 비토) 나 彼氏 (かれし, 카레시, 남자친구), 彼女 (かのじょ, 카노조, 여자친구) 라고 해야 한다.
  • 迷惑(めいわく, 메이 와쿠) - 한국어로는 "미혹"이란 말로 무언가에 홀리는 것을 얘기하지만, 일본에서는 남에게 폐를 끼치거나, 번거롭게 한다는 의미로 쓰인다. "당신을 헤메고 혹하게 해 미안합니다"의 의미로 받아 들임 어떨까 싶다.
  • 真面目(まじめ, 마 지 메) - 한국어로는 "진면목"이란 단어로 "무언가의 진짜 모습(좋거나 나쁠 수 있음)" 이라는 느낌이지만 일본어로는 착실하다, 진지하다의 표현으로 쓰인다.

경험이 많진 않지만, 이렇게 완전히 다른 의미로 쓰는 단어들은 아주 많진 않은 듯 하긴 하다.


그 다음으로는 우리가 쓰는 한자와 다른 한자 단어를 쓰는 경우의 몇 가지 예를 봐보자

  • 風邪(かぜ, 카제, 감기) - 우리는 감기(感氣) 라고 표현한다. "(차가운) 바람(風)으로 생기는 나쁜 일(邪)" 이니 감기의 의미는 맞을 듯 하다.
  • 紅葉(こうよう, 코우 요우, 단풍) - 우리는 단풍(丹楓)이라고 표현한다. "붉은 잎"이니 단풍의 느낌이 난다. 같은 한자를 단풍잎을 의미할때는 (もみじ, 모미지) 라고도 읽는다.
  • 郵便局 (ゆうびんきょく, 유우 빈 쿄쿠, 우체국) - 우리는 우체국(郵遞局) 이라고 쓰는데, 여기는 우편국 이라고 쓴다.
  • 長所(ちょうしょ, 쵸우 쇼, 장점) - 우리는 장점(長點)이라고 표현하는데, 여기서는 "잘하는 바(所)" 라고 표현한다. 해석 측면에서는 비슷하다.
  • 日傘(ひがさ, 히 가사, 양산) - 마찬가지로 우리는 양산(陽傘)이라고 표현하는 데, 일본은 "날 일"자(우리도 일출, 일광욕 등에서는 해를 의미한다)를 써서 "일산" 이라고 쓴다.

 


또 일상어 중에 아예 영어로 대신 표현하는 가타가나 단어를 쓰는 경우도 많은 듯하다.

  • ピアス (피 아 스, 뚫는 귀걸이) - 영어의 pierce 에서 왔다고 한다, 귀에 거는 귀걸이는 (イヤリング, 이야링구, earring) 라고도 한다.
  • ハンカチ (한 카 치, 손수건) - 영어의 handkerchief 에서 왔다고 한다.
  • スーツ (수- 츠, 양복) - 영어의 suit 에서 왔다고 한다.
  • デパート (데 파- 토, 백화점) - 영어의 department store 의 앞 부분에서 왔다고 한다.

아래와 같이 한국에서는 좀 낯선 단어도 외래어로 자연스럽게 사용한다.

  • アイロン (아이 론, 다리미질) - 영어의 iron 에서 왔다. アイロンをかける(~오 카케루) 하면 다리미질을 하다의 의미가 된다.



그 다음엔 우리 한자 단어에는 없거나 잘 안 쓰이지만 일본어에는 잘 쓰이는 단어들이 있다. 

  • 邪魔(じゃま, 쟈 마, 폐) - 우리나라에서는 환타지나 무협 소설에서나 쓸 "사마"라는 단어이지만, 일본어에서는 폐를 끼치거나 방해하는 것을 의미한다. 뭐 "나쁜 일"을 끼친다고 생각하면 어떨까 싶다. 
  • 結構(けっこう, 겟 코우) - 이것도 예전 고전 시구에서나 언급 될 "결구"라는 단어이지만, 일본어에서는 "괜찮다"는 의미거나, "꽤" 라는 의미로 쓰인다. "무언가를 얽어서 구조를 만드니" 모양이 잘 잡혀서 괜찮다는 정도의 느낌이라고 생각함 어떨까 싶다. 
  • 大切(たいせつ, 다이 세츠) - 우리나라 한자로 읽음 "대절"이라고 특별한 의미를 띄지 않지만, 일본어에서는 중요하거나 소중한 것을 얘기한다. 우리도 "절박(切迫)" 하다는 표현을 쓰기도 하니 "크게 절박한 것" 정도의 의미일 것이다
  • 残念(ざんねん, 잔 넹) - 이것도 심령 드라마에서 나올 "잔념"이라는 단어지만, 일본어에서는 아쉽거나 안타까움을 표현한다. "(아쉬운) 생각이 남는다" 정도면 말이 될 것 같다.
  • 我慢(がまん, 가 만) - 우리나라 한자로는 "아만" 이라는 말로 특별한 의미가 못 되지만, 일본어에서는 무언가를 참거나 견디는 것을 얘기한다. 이건 "자신의 오만함"이라는 불교의 번뇌 중 하나를 나타내는 말이라는데, 오만함이 자존심을 지키는 참을성의 의미로 반어적으로 바뀐 걸까 싶다.



여담으로 우리가 일상에서 많이 보는 일본어도 어원을 따지고 보면 단순해서 재밌는 경우도 있다.

  • お好み焼き (おこのみやき, 오 코노미 야키) - 좋아하는 것을(お好み) (모아서) 굽기(焼き)
  • とんかつ (豚かつ, 톤 카츠, 돈까스) - 돼지고기(豚)로 만든 커틀렛(カツレツ, 카츠레츠, cutlet)
  • 照り焼き (てりやき, 테리 야키, 데리야키) - 소스를 발라 반짝거리게(照り) 굽기(焼き)
  • 居酒屋 (いざかや, 이 자카 야, 일본식 술집) - 술(酒)을 마시면서 머무르는(居) 집(屋)



비슷한 지역과 문화권 이기 때문에 속담이나 사자성어 등도 비슷하거나 같은 경우가 당연히 있을 것이다.,아래와 같이 완전 같거나 어디서 들어본 듯한 비슷한 표현도 있고,

  • 花より団子 (はなよりだんご, 하나 요리 단고) - 예쁜 꽃(花) 보다는(より) 경단(団子)이 좋다. 금강산도 식후경. 요즘으로 따지면 좀 더 실속 있는 것을 추구하는 부분이라고 볼 수 있다.
  • 苦あれば楽あり (くあれば らくあり, 쿠 아레바 라쿠 아리) - 고통이(苦) 있다면(あれば) 즐거움(楽)도 있다(あり). 고생 끝에 낙이 온다.
  • 灯台下暗し (とうだいもとくらし, 토우다이 모토 쿠라시) - 등잔(灯台) 밑이(下) 어둡다(暗し)
  • 泣き面に蜂 (なきつらにはち, 나키 츠라니 하치) - 우는(泣き) 얼굴(面)에(に) 벌(蜂)이 쏜다. 엎친 데 덮친 격 또는 설상가상 정도.
  • 猿も木から落ちる (さるもきからおちる, 사루 모 키 카라 오치루) - 원숭이(猿)도(も) 나무(木)에서(から) 떨어진다(落ちる).



실제 동일한 사자성어 등도 꽤 많은 듯 한다.

  • 五十歩百歩 (ごじゅっぽひゃっぽ, 고 쥿 뽀 햣 뽀) - 오십보백보
  • 自業自得 (じごうじとく, 지 고우 지 토쿠) - 자업자득
  • 以心伝心 (いしんでんしん, 이 신 덴 신) - 이심전심
  • 天真爛漫(てんしんらんまん, 텐 신 란 만) - 천진난만



반대로 일부의 표현이 다른 사자성어도 있다.

  • 一喜一憂(いっきいちゆう, 잇 키 이치 유우) - 일희일비(一喜一悲) 로 "근심 우(憂)"가 아닌 "슬플 비(悲)" 를 우리는 쓴다
  • 他山の石(たざんのいし, 타 잔 노 이시) - 타산지석(他山之石) 으로 之(~의)의 일본 뜻인 の 로 대체했다.
  • 鬼に金棒(おににかなぼう, 오니 니 카나보우) - 우리로 따지만 금상첨화(錦上添花) 정도로 도깨비가 방망이를 들었으니 원래도 훌륭한데 더 없이 완벽하다는 얘기다.
  • 一炊の夢(いっすいのゆめ, 잇 수이 노 유메) - 일장춘몽(一場春夢) 을 "한 바탕의 꿈이" 아니라 "불땔 취(炊)" 를 사용하여 "한번 불이 붙었다 사라지는 꿈"으로 표시한다.

중국어도 비슷하지만, 사람들의 생각과 삶은 사실 많이 비슷하기 때문에, 결국 한자 단어나 일본어 자체를 가지고 비슷한 표현들을 한다고 보면 될 것 같다.



다음엔 중국어를 봐보자. 중국어도 처음 시작하면 우리나라 한자를 좀 안 다는 가정에서 뭔가 버프를 받는 듯한 느낌이 들지만, 세상엔 공짜는 없어서 그 때문에 진행하다가 종종 이게 뭐지 하는 당황스런 상황도 만나게 된다.

생각해 보면 우리가 쓰는 한자는 중국의 어떤 시대의 한 스냅샷을 가져와서 한글과 결합해서 의미를 담아 오면서 내려왔고(이건 일본도 마찬가지다), 중국의 한자 단어는 우리의 고어와 지금의 말이 많이 차이나 듯이, 이후에도 계속적으로 현대까지 변했을 테니까 당연할 것 같긴 하다. 게다가 중국어의 한자는 좀 더 말랑말랑 하기까지 하니까 말이다. 이 부분은 그냥 두 한자가 조금은 쓰임이 다르다고 받아들여 보자.

그래서 아예 모르는 신규 글자의 단어라면 그냥 받아들이면 되는데, 한자를 아는 입장으로 우리가 아는 한자 단어와 아예 다른 뜻으로 쓰이는 경우도 있고, 우리가 쓰는 의미를 가지면서 동시에 때론 다른 뜻으로 많이 쓰이는 경우도 있다. 


딱딱한 얘기를 하기 전에(뭐 이것도 딱딱할 수 있겠지만) 우선 드라마의 대사 몇 개를 보자. 偷偷藏不住(tōutōucángbùzhù, 토우 토우 창 부 주) 라는 중국 드라마이다. "몰래(偷偷) 좋아하는 감정을 숨길 수 없음(藏不住)" 이란 뜻으로 한국 제목은 "너를 좋아해"이다. 기존 예를 들었던 문장들 보다는 조금 길게 되지만, 뒤에서 얘기하고 싶은 내용이 이 몇 개의 대사에 함축되어 있다. 

 

  • 我爸妈今天临时要去工地。(Wǒ bà mā jīntiān línshí yào qù gōngdì, 워 빠 마 진티엔 린스 야오 취 공띠). 

남자 주인공이 말썽을 부린 여자 주인공 학교에 친 오빠인 척 가서 선생님과 면담하는 장면이다. 아는 한국 한자의 뜻을 기반으로 해석하면 "엄마 아빠가(我爸妈) 오늘(今天) 임시로(临时) 공사장(工地)에 가야 해서요(要去)" 가 되는데 이럼 문장이 뭔가 이상한 느낌이 든다. 이 경우 임시(临时)는 "임시 -> 평소가 아닌 상황 -> 갑자기" 라는 의미로 중국어에서는 많이 쓰이기 때문에, 대사의 뜻은 "엄마 아빠가 오늘 갑자기 공사장에 가야되서요(드라마 내의 부모님 직업이 건설 쪽 인 듯 싶다)"가 된다.

  • 让他好好做做桑椎的思想工作。(ràng tā hǎohǎo zuòzuò sāngzhì de sīxiǎng gōngzuò, 랑 타 하오하오 쭈어쭈어 쌍즈 더 스샹 꽁쭈어)

"그로 하여금(让他) 샹즈(桑椎, 여자 주인공)를 사상공작(思想工作)을 잘(好好) 하게 해라(做做)"라는 해석이 된다. 연애 드라마에 무슨 스파이 장르 대사가 있냐 싶지만, 말랑말랑한 중국 한자의 의미로는 思想工作 는 "마음(思想)을 돌리도록 누군 가를 잘 타이르거나 설득한다(工作)"는 의미가 있다. 그래서 위의 표현은 상황에 따라 "그 한테 샹즈를 잘 다독거려 설득하도록 해봐" 정도의 부드러운 일상 표현이 된다. 

  • 我的心里也踏实。(wǒ de xīnlǐ yě tāshi. 워 더 신리 예 타스)

뒤에 한국에서는 처음 보는 踏实 라는 단어가 온다. 근데 한자의 의미를 생각해 본다면 밟을 답(踏)에 열매 실(实) 이다. 이렇게 보면 뭔가 차곡차곡 밟아서 튼실하게 되는 건가 싶은데, 중국어에서 踏实 의 뜻은 "발 밑이 단단하다 > 단단한 땅을 딛고 있다 > 마음이 편하다"의 의미로 "마음이 놓이다, 안정되다"의 의미라서 해당 문장의 의미는 "내(我的) 마음(心里)도(也) 편안해(踏实)" 정도가 된다.

  • 所以...你刚刚是专门去给我买糖去了?(suǒyǐ...nǐ gānggāng shì zhuānmén qù gěi wǒ mǎi táng qùle?, 수어 이... 니 깡깡 스 쫜먼 취 게이 워 마이 탕 취러?)

아는 한자 뜻 대로 해석하자면 "그럼(所以)...너(你) 방금(刚刚) 나에게 전문적으로(是专门去) 사탕(糖) 사주러(买) 간거야(去了)?" 야 되는데 이렇게 해석함 일단 연애 드라마 속의 대화 느낌은 아닌 듯 싶다. 여기서도 专门 이라는 의미가 무언가를 전문적으로 한다는 거에서 더 나아가 "전문적으로 > 오로지 > 일부러", 또는 "~를 위해 특별하게" 정도의 의미가 되어서 "너 방금 나 주려고 일부러 사탕 사러 갔다 온 거야?" 의 의미가 된다. 

이렇게 보면 기존의 한자를 아는 게 오히려 발목을 잡는 듯한 느낌도 조금 생긴다. 이런 뉘앙스가 다른 한자를 사용하는 문장들은 한국 사람 입장에서는 왠지 어휘를 잘못 쓴 뜻이 어색한 문장 같은 위화감이 느껴지기 때문이다. 

그럼 이제 위의 대사를 마음에 두고, 한자의 차이에 따라 개인적으로 나눠 본 몇 가지 한자 단어 그룹 들을 봐보자

 
우선 한자가 우리나라와 같은 모양이지만 다르게 쓰이는 경우가 있다(때로는 비슷하게도 쓸 때도 있다).

  • 礼物 (lǐwù, 리 우, 선물) - 한자만 보면 우리가 결혼식 등에서 보는 예물(禮物)이다. 중국어에서는 보통 일반적으로 주는 "선물"의 의미로 쓰이는 듯 하다.
  • 清楚 (qīngchu, 칭 추, 분명하다/뚜렷하다) - 이 표현은 여자나 자연 등에 대해서 "청초(淸楚)하다" 라는 표현으로 쓰지만(아침 이슬이 청초하다), 중국에서는 "분명하거나 뚜렷하다"는 의미로만 쓰인다.
  • 明白 (míngbai, 밍 바이) - 중국에서는 한국어에서 쓰이듯 "명백"하다는 의미로는 거의 안 쓰이고, "알다, 이해하다"의 의미로 자주 쓰이는 듯하다. 我明白了(wǒ míngbái le, 워 밍바이 러) 하면 "알겠습니다"라는 의미가 된다.
  • 结实 (jiēshi. 지에 스, 튼튼하다) - 우리나라에서는 "결실을 맺다"라는 의미로 쓰지만, 중국어에서는 해당 의미로는 안 쓰고("결실을 맺다"의 의미로는 "과실 과"를 뒤에 넣어 - 结果, jiéguǒ, 지에 구어 - 를 대신 쓰는 듯 하다), 보통 물건이 튼튼하거나 질긴 상태를 표현한다.



뜻은 같지만 순서가 반대로 뒤집힌 한자들도 종종 있다. 한자가 해석되는 순서 땜에 그렇다고도 하고, 전해 내려오면서 그렇게 굳어졌을 수도 있지만 이 경우도 은근 헷갈리는 경우가 많다. 게다가 온라인 중국어 사전에는 반대 순서의 단어도 사용하고 있다고 같이 검색되거나, 관련 예문이 있는 경우도 많은 듯해 확실하지도 않고, 특히 작문 같은 거 할 때는 쥐약이다. 이것도 결국 문장 속에서 익혀야 하는 부분 같고, 요즘 같은 경우는 AI 검색 엔진한테 물어보면 거짓말도 자연스럽게 하지만 몇 번 집요하게 물어도 강한 어조로 얘기 한다면 참고할 만 하며, 가장 좋은 방법은 감이 있는 그 나라 사람한테 물어보는 것일 것이다.

  • 避免 (bìmiǎn, 비 미엔, 모면하다) - 한국어에서는 면피(免避)로 쓰인다. 
  • 命运 (mìngyùn, 밍 윈, 운명) - 한국어에서는 사주에서 쓰는 명운이라는 용어도 있지만, 운명(運命)으로 보통 쓰인다.
  • 缩短 (suōduǎn, 수오 똰, 단축하다) - 한국어에서는 단축(短縮) 으로 쓰인다.




낯설지만 한자의 뜻을 알면 우리가 사용하는 한자의 뜻과 비슷해 유추할 수 있는 단어들도 있다. 해석의 전개는 마음대로 해봤다.

  • 后果 (hòuguǒ, 호우 구어) - 뒤 후(后), 실과 과(果) -> 뒤의 결과 -> (주로 나쁜) 일의 결과
  • 怀念 (huáiniàn, 화이 니엔) - 품을 회(怀), 생각할 념(念) -> 마음을 품다 -> 그리워 하다
  • 失去 (shīqù, 스 취) - 잃을 실(失), 갈 거(去) -> 잃은 상태로 지나가다 -> 잃다, 잃어버리다
  • 事先 (shìxiān, 스 시엔) - 일 사(事), 먼저 선(先) -> 일에 앞서서 -> 사전에
  • 受伤 (shòushāng, 쇼유 샹) - 받을 수(受), 다칠 상(伤) -> 상처를 받다 -> 상처를 입다, 손상을 입다
  • 完善 (wánshàn, 완 샨) - 완전할 완(完), 착할 선(善) -> 완전하게 좋다 -> 완전하다, 완비하다 
  • 估计 (gūjì, 구 지) - 값 놓을 고(估-추측하다), 셀 계(计) – 추측하고 계산하다 -> 추측하다, 예측하다



한국에서는 사용되지 않는 낯설게 느껴지는 중국어 조합 단어들이 있다. 다만 말랑말랑한 한자를 느낄 수 있다면 유추가 가능할 것은 같다.

  • 倒闭 (dǎobì, 다오 비) - 넘어질 도(倒-실패하다), 닫을 폐(闭) -> 넘어져서 닫다 -> (기업체가)도산하다
  • 不耐烦 (búnàifán, 부 나이 판) - 아닐 부(不), 견딜 내(耐), 괴로워할 번(烦) -> 괴로움을 견디지 못하다 -> 못 참다, 성가시다
  • 超级 (chāojí, 차오 지) - 뛰어넘을 초(超), 级(등급) -> 뛰어넘은 등급 -> 뛰어난, super, 초~
  • 拐弯 (guǎiwān, 과이 완) - 속일 괴(拐-방향을 바꾸다), 굽을 만(弯) -> 굽으면서 방향을 돌다 -> 방향을 바꾸다, 모퉁이
  • 救护车 (jiùhùchē, 지우 후 처) - 구원할 구(救), 지킬 호(护), 수레 차(车) -> 구해서 지켜주는 차 -> 구급차
  • 体会 (tǐhuì, 티 회이) - 몸 체(体-체득하다), 모일 회(会-이해하다) -> 체득하다, 이해하다
  • 销售 (xiāoshòu, 시아오 쇼우) - 녹을 소(销-팔다/소비하다), 팔 수(售) -> 팔다, 판매하다
  • 满意 (mǎnyì, 만 이) - 찰 만(满), 뜻 의(意) -> 가득찬 마음 -> 만족하다, 맘에 들다
  • 排队 (páiduì 파이 뛔이) - 늘어설 배(排), 대오 대(队) -> 늘어서 열을 이루다 -> 줄을 서다



우리나라는 옛날 사극에나 나오거나 사자성어나 설명체 등의 무거운 뉘앙스로 쓰는데 중국어는 평상어 인것도 종종 보인다.

  • 允许 (yǔnxǔ, 윈 쉬) – 윤허하다(임금이 신하한테 허락하는 거) -> 허가하다, 허락하다
  • 前途 (qiántú, 치엔 투) – 전도양양 하다는 표현으로 주로 씀 -> 유망한 앞길, 진로
  • 留言 (liúyán, 리우 옌) – 우리나라는 죽을 때 남기는 유언이지만 -> 중국은 메시지를 남겨주세요 같은 걸 남긴다.
  • 转移 (zhuǎnyí, 쫜 이) – 병이 전이 되거나, 물체가 옮겨가는 것 -> 옮기다, (화제 등을) 바꾸다 정도인 듯하다



물론 아래와 같이 간체로 모양만 조금 다르지, 뜻이 동일한 경우도 많이 있다. 이게 딱 분리되어 다르게 쓰는 한자와 구분이 잘 안된다는 게 문제이긴 하지만 말이다. 그리고 간체에 익숙해질 수록 가끔 원래 번체와 다른 단어인가 하고 같은 글자임을 인지 못 할 때도 있다.

  • 促进 (cùjìn, 추 진) – 촉진하다(促進)
  • 记忆 (jìyì, 지 이) – 기억하다(記憶)
  • 会计 (kuàijì, 콰이 지) – 회계(會計)
  • 平安 (píng’ān, 핑 안) – 평안하다
  • 色彩 (sècǎi, 서 차이) – 색체

 


한국에서는 쓰지만 중국어에서는 사용 안 하는 단어들도 있다. 이런 거 섞어 쓰면 조금 이상한 외국 사람 되는 걸거다.

  • 대학교(大學校) – 大学(dàxué, 따 쉐)
  • 학부모(學父母) – 家长(jiāzhǎng, 찌아 장) 집의 어른이라는 의미이다
  • 이사(移徙) – 搬家(bānjiā, 반 지아) 집을 옮기다는 의미이다
  • 선배 (先輩) – 前辈(qiánbèi, 치엔 베이, 보통 직장에서의 선배) 한국 사람에게는 무협소설에서나 나올 한자 조합이다. 이것도 사실 잘 안 쓰고 哥(gē, 꺼), 妹(mèi, 메이)를 쓴 다고도 한다. 우리가 일본식 한자 표현 얘기하듯이, 중국에서도 한국식, 일본식 한자 표현이라고 얘기를 하나 보다.
  • 우유 (牛乳)- 牛奶(niúnǎi, 니우 나이) - 젓 유(乳)가 아닌 젓 내(奶)를 쓴다



특정한 이미지를 나타내어 결합하기 때문에 알아 두면 이곳 저곳에서 응용해 쓸 수 있는 한자들이 있다. 

  • 死 (si, 스, ~해서 죽겠다) - 急死(jí‧si, 지 스, 급해 죽겠다), 想死(xiǎngsǐ, 샹 스, 보고 싶어 죽겠다)
  • 白 (bái, 바이, 헛수고 했다는 것) - 白搭(báidā, 바이 다, 소용 없다, 헛수고하다), 白干(báigàn, 헛 일 하
  • 小 (xiǎo, 샤오, ~군, ~양 정도) - 小王(xiǎowáng, 샤오 왕, 왕 군), 小李(xiǎolĭ, 샤오 리, 이 양)
  • 红 (hóng, 홍, 인기 있다) - 网红(wǎnghóng, 왕홍, 인터넷에서 유명한 사람, 인플루언서)
  • 花 (huā, 화, 돈을 쓰다) - 花钱(huāqián, 화 치엔, 돈을 쓰다), 花光(huāguāng, 화 꽝, 앞의 光와 결합하여 돈을 모두 탕진하다)
  • 吃 (chī, 츠, 받다/감수하다) - 吃亏(chīkuī, 츠 쿠이, 손해를 보다), 吃苦(chīkǔ, 고생하다)



영어 동사처럼 여러 의미로 확장되는 한자들이 있다. 뭐 한글에도 그렇게 비유적인 표현들이 있기도 하고, 뭐 앞에서 얘기했듯 말랑말랑한 한자이다. 

  • 偷 (tōu, 훔칠 투) - 훔치다 > 남몰래
  • 瞎 (xiā, 시아, 애꾸눈 할) - 눈이 멀다 > 뵈는 게 없다 > 아무렇게나, 제멋대로
  • 挣 (zhèng, 타툴 쟁) - 다투다 > 필사적으로 애쓰다 > 돈을 벌다
  • 仍 (réng, 인할 잉) - 그대로 따르다 > 빈번하다 > 아직도
  • 捐 (juān, 버릴 연) - 버리다 > 바치다 > 기부하다 捐款(juānkuǎn, 좐 콴)
  • 图 (tú, 그림 도) - 그림을 그리다 > 도모하다, 계획하다
  • 脆 (cuì, 추이, 연할 취) 무르다 > 푸석푸석하다 > 사각사각 하다



한자도 읽을 수 있고 한자의 뜻도 알고 있지만, 어떤 뜻으로 쓰이는지 문장을 이해하기 전에는 알 수 없는 단어들이 있을 수 있다(다만 알고 나서 뜯어 보면 그럴 수 있겠다 라는 생각은 든다).

  • 外行 (wàiháng, 와이 항) - 줄 항(行-직업, 업무의 의미도 있음), 밖 외(外) -> 업무의 바깥에 있다, 동업자가 아니다 -> 잘 모르거나 아마추어의 의미로 확장됨, 문외한이다
  • 在行 (zàiháng, 짜이 항) - (반대로) 업무에 자리하고(在) 있다 -> 능하다, 정통하다
  • 粗心 (cūxīn, 추 신) - 거칠 조(粗-거칠고 조잡하다, 꼼꼼하지 못하다), 마음 심(心) -> 꼼꼼하지 못한 마음 -> 부주의하다/세심하지 못하다.
  • 小票 (xiǎopiào, 샤오 피아오) - 작을 소(小), 쪽지 표(票-증서) -> 작은 증서 -> 영수증
  • 熬夜 (áoyè, 아오 예) - 볶을 오(熬-오랫동안 끓이다, 달이다), 밤 야(夜) -> 밤에 오랫동안 달이다 -> 밤새다
  • 借口 (jièkǒu, 지에 코우) - 빌릴 차(借), 입 구(口) -> 입(口)에 의지해서 요리조리 빠져나가다 -> 구실로 삼다, 핑계로 삼다
  • 零食 (língshí, 링 스) - 나머지 령(零-아주 자질구레한 것), 먹을 식(食) -> 자질구레한 식사 -> 간식
  • 小气 (xiǎo‧qi, 샤오 치) - 작을 소(小), 기운 기(气) -> 기질이 작다 -> 인색하다
  • 难看 (nánkàn, 난 칸) - 어려울 난(难), 볼 간(看) -> 좋지 않게 보인다 -> 안색이 좋지 않다, 못생기다
  • 养车 (yǎngchē, 양 처) - 기를 양(养), 수레 차(车) -> 차를 부양하다(아이, 동물 처럼 잘 키우다, 쓰다) -> 차를 정비하고 관리하다
  • 麻花 (máhuā, 마 화) - 삼 마(麻), 꽃 화(花) -> 마를 꼬듯이 꼬아 놓은 꽃 모양의 음식 -> 꽈배기



마지막으로 사자성어 들을 살펴 보면서 마무리 하자. 한국과 같은 사자성어들도 있다.

  • 同病相怜 (tóng bìng xiāng lián, 통 빙 샹 린) - 동병상련
  • 自暴自弃 (zì bào zì qì, 즈 빠오 즈 치) - 자포자기



문제는 일본어 때도 그랬지만 미묘하게 일부 글자를 다르게 쓰는 사자성어들이 종종 보이게 된다.

  • 表里不一 (biǎo lǐ bù yī, 비아오 리 부 이) - 한국은 표리부동(表裏不同) 으로 "한 일" 대신 "같을 동"을 쓴다
  • 好事多磨 (hǎo shì duō mó, 하오 스 뚜어 모) - 한국은 호사다마(好事多魔)에서 "마귀 마"가 아닌 "갈 마(시련이 많다)"를 쓴다.
  • 坐收渔利 (zuò shōu yú lì, 쭈어 쇼우 위 리) - 한국의 어부지리(漁夫之利)가 아닌 "앉아서 물고기를 줏는 이득을 얻다" 이다.
  • 目中无人 (mù zhōng wú rén, 무 쫑 우 런) - 한국은 안하무인(眼下無人) 으로 "눈 아래"가 아닌 "눈 안"에 사람이 없다로 표현한다.
  • 独一无二 (dú yī wú èr, 두 이 우 얼) - 한국은 유일무이(唯一無二) 로 "오직 유(唯)" 대신 "홀로 독(独)"을 쓴다.



일단 시험을 보거나 게임을 하거나 드라마를 보게 되도 사자성어는 정말 많이 나온다. 중국에서의 사자성어는 많은 속담이나 생활의 느낌, 특별한 생각들이 몇 글자로 압축되어 있다고 보면 어떨까 싶다. 우리가 걍 말 버릇처럼 "시작이 반이다", "집에서 새는 바가지..." 어쩌고 하는 것과 비슷하다고 생각하면 된다.

  • 半途而废 (bàn tú ér fèi, 반 두 얼 페이) - 중간(半途)에서(而) 그만두다(废)
  • 不屑一顾 [bú xiè yí gù] 가루(屑) 하나도 거들떠볼(一顾) 필요도 없다(不)
  • 得不偿失 [dé bù cháng shī] 얻는 것(得)이 잃는 것(偿失)보다 못하다(不)
  • 急功近利 [jí gōng jìn lì] 눈 앞의(急) 성공(功)과 당장의(近) 이익(利)에만 급급하다
  • 争先恐后 [zhēng xiān kǒng hòu] 앞을 다투고(争先) 뒤로 처지는 것을 두려워 하다(恐后)
  • 马马虎虎 [mǎ‧mǎhūhū] 대충대충 하다 - 말을 그리던 화가가 다른 사람이 호랑이를 그려달랬더니 두 개를 섞어 대충대충 그리고 얼버무렸다고 해서 생겼다는 설이...



- Fin - 

posted by 자유로운설탕
2025. 7. 29. 19:10 일본어와 중국어

이번엔 명확한 규칙이 있다고는 할 순 없겠지만 한자의 발음에 대해서 한국어 발음이랑 비교해 한번 얘기해 보려 한다.
앞에서 한국어와 일본어는 문법이 엄청 유사하다고 얘기했지만, 거기에 추가로 한자의 발음도 꽤 유사하다. 
발음이 비슷하게 들린다는 측면도 조금은 있긴 하지만, 한자 단어의 경우 글자가 다르더라도 같은 한국어 발음으로 시작되는 서로 다른 한자들이 일본어에서도 한 두 개 정도의 그룹으로 묶이는 경우가 많다.

예를 들어 한국어에서 "과" 발음의 한자 단어 들을 보자(서로 다른 글자가 같은 발음으로 묶인 다는 걸 보이기 위해서 예제를 일부러 좀 많이 넣었다)

  • 科学 (과학, かがく, 가쿠)
  • 果汁 (과즙, かじゅう, 쥬우)
  • 課題 (과제, かだい, 다이)
  • 過程 (과정, かてい, 테이)
  • 過去 (과거, かこ, 코) 
  • 効果 (효과, こうか, 코우 )
  • 寡黙 (과묵, かもく, 모쿠) 
  • 製菓 (제과, せいか, 세이 )


발음이 달라지는 한자들도 있는데, 이 경우라도 "카" -> "코" 정도로 뭐 이해해 줄만 하다. 

  • 誇張(과장, こちょう, 쵸우)

 


다른 예로 "문"이라고 발음 되는 한자들을 보자.

  • 文章 (문장, ぶんしょう, 쇼우)
  • 新聞 (신문, しんぶん, 신 )
  • 紊乱 (문란, ぶんらん,  란)

  • 専門 (전문, せんもん, 센 )
  • 質問 (질문, しつもん - 시츠 )
  • 紋様 (문양, もんよう -  요우)

이 경우도 "분" 이나 "몬" 으로 발음 되기 때문에 기존 한글 발음인 "문"의 범위 내에서 유사하게 발음 되는 느낌이 있다. 그래서 일본어를 좀 많이 듣다 보면 한자 단어의 경우 이 단어 같다는 추측이 가능하게 되는 상황이 생긴다.


다만 아쉽게도 한자에 따라서 뜻을 이용해 읽는 경우는 예외적인 상황이 생긴다. 아래와 같이 明 한자는 めい(메이), 日 한자는 にち(니치) 라고 한자 발음으로 읽히는데 

  • 明白 (명백, めいはく - 메이 하쿠)
  • 明細 (명세, めいさい - 메이 사이)

  • 日時 (일시, にちじ - 니치 지)
  • 日曜 (일요, にちよう - 니치 요우)

두 개의 단어가 결합된 明日는 めいにち(메이 니치) 라고 되면 참 좋을텐데, 내일이라는 고유어를 표현하는 あした(아시타) 라고 읽는다.


게다가 조금 더 불행한 부분은 위의 日 경우도 어떤 소리와 한자와 결합되느냐에 따라 마치 우리나라 된소리 비슷한 촉음화라는 받침이 생기기도 하고

  • 日記 (일기, にっき - 키)
  • 日課 (일과, にっか -  카)

아래와 같이 같이 반대로 발음이 부드러워 질 수도 있다.

  • 日本 (일본, にほん - 혼)



또한 이렇게 다양하게 변하는 경우가 일반적인 케이스는 아니지만, 아래와 같이 다양한 예외가 나올 수 있다.
음 독 이면서도 다른 발음 일수도 있고,

  • 休日 (휴일, きゅうじつ, 큐우 지츠)


다른 훈 독 인 해라는 의미와 결합되면 아래와 같이 히나 비로 발음 된다.

  • 日差し (햇볕, ひざし, 자시)
  • 記念日 (기념일, きねんび - 키 넨 )



단어 중 한자의 모양을 갖추었지만 고유어의 의미로 읽히는 경우가 많이 있는데, 뭐 이건 그때 그때 보면서 외워야 하는 거지 싶다. 언어가 뭐 항상 논리적으로 설명되진 않으니까 말이다.

  • 大人 (어른, おとな, 오토나)
  • 下手 (서툴다, へた, 헤타)
  • 紅葉 (단풍, もみじ, 모미지)
  • 着物 (키모노, きもの, 키모노)

 

 



중국어는 약간 상황이 달라서 처음 배우게 되면 발음이 많이 다른 것 같이 느껴진다.

 

예를 들어 "우"라는 한자를 보자

  • 优秀(우수, yōuxiù, 요우 시우) - 우수하다
  • 右 (우, yòu, 요우)

  • 雨伞 (우산, yǔsǎn, 산)
  • 待遇 (대우, dàiyù, 따이 )

  • 牛肉 (우육 niúròu, 니우 로우)

이렇게 보면 "요우"랑, "위" 는 그러려니 하는데, "니우" 발음으로 가면 발음이 완전 다른가 하는 생각이 들 수 있지만, 일본어 보다 조금 가지가 몇 개 더 늘어나는 측면이 있다고 받아들이면 매칭 되는 부분이 비슷하게 있는 것 같다.


그럼 그런 생각 하에 다른 발음 "과" 하나를 더 보자 

  • 水果 (과일, shuǐguǒ - 수이 꾸어)
  • 过去 (과취, guòqù, 꾸어 취) - 지나가다의 의미
  • 通过 (통과, tōngguò - 통 구어) - 통과하다의 의미

  • 西瓜 (시과, xīguā, 시 ) - 수박
  • 黄瓜 (황과, huángguā - 황 ) - 오이

  • 夸张 (과장, kuāzhāng - 장) - 과장하다의 의미
  • 跨行 (과행, kuàháng,  항) - 타행의 의미

 

이럼에도 불구하고 일본어 보다 하나 좋은 점은 음 발음, 뜻 발음 같은 베리에이션이 없기 때문에 항상 일정한 발음으로 읽혀지는 부분이지만, 추가로 성조라는 게 있기 때문에 또 그만큼 주의해야 할 건 늘어나서 비슷한 것 같기도 하고, 밑에서 얘기하는 대로 살짝 예외가 있다.


하나의 글자의 발음은 항상 일정하다는 생각을 가지고 공부하다 보면 글자가 분명이 같은데, 발음이 달라지거나, 성조가 달라져서 이게 뭐지 하고 헷갈리는 경우가 있다. 

 

해당 상황이 발생하는 이유는, 간체를 만들면서 서로 다른 글자가 합쳐지거나 하는 이유로 글자 모양은 같지만 실제는 서로 다른 한자의 뜻으로 쓰여질 때 그런 것 같다. 이런 하나의 글자가 성조나 발음이 서로 다른 경우는 아주 많진 않아서(아마 100개 미만 이지 않을까 싶다), 이상할 때 마다 정리하거나 그냥 아무 생각 없이 단어를 외거나 하면 될 정도인 것은 같다.

발음이나 성조가 달라지는 몇 개의 예를 보자.

발음 까지 달라지거나 

  • 长 (cháng, 창, 길 장, 延长, yáncháng, 옌 창, 연장하다) - 한국 한자로는 镸 로 표기한다.
  • 长 (zhǎng, 짱, 어른 장, 成长, chéngzhǎng, 청 짱, 성장하다, 자라다) - 한국 한자로는 𠀋 로 표기한다

  • 行 (xíng, 걸을 행, 旅行, lǚxíng, 뤼 싱, 여행)
  • 行 (háng, 줄 항, 银行, yínháng, 인 항, 은행)

  • 乐 (lè, 러, 즐거울 락, 快乐, kuàilè, 콰이 러, 즐겁다) - 한국 한자로는 둘 다 樂 로 표기한다.
  • 乐 (yuè, 웨, 풍류 악, 音乐, yīnyuè, 인 웨, 음악)

때론 성조만 달라지기도 한다. 뭐 모양만 같지 서로 다른 한자이니, 두 한자가 발음이 같으면서 성조만 다른 경우라고 보면 된다.

  • 假 (jiǎ, 지아, 거짓 가, 假装, jiǎzhuāng, 지아 쫭, 가장하다, ~인 체하다) 
  • 假 [jià, 지아, 틈 가, 请假, qǐngjià, 칭 지아, 휴가를 내다) - 한국 한자로는 暇 로 표기한다.

  • 中 (zhōng, 쫑, 가운데 중, 中间, zhōngjiān, 쫑 지엔, 중앙, 중간)
  • 中 (zhòng, 쫑, 맞을 중, 命中, mìngzhòng, 밍 쫑, 명중하다)


정리해 보면 한국인 입장에서 한자 발음을 읽을 때 두 언어다 어느 정도 공부하다 보면 발음이 어느 정도 유추가 되는 시점이 오는 것은 맞는 듯 싶다. 사실 이건 두 나라 모두 특정 시대의 중국 한자 발음을 나름 들리는 대로 가져와 고유 말 발음과 매칭 시켰을 터이니 당연한 것 같기도 하다. 다만 일본어의 경우는 훈독이, 중국어의 경우는 성조가 이 편안함에 조금 후추 가루를 뿌리는 경향이 있다.

강사 분의 얘기로는, 요즘 사람들은 한자 자체를 잘 모르는 경우도 많기 때문에 그냥 한국 한자의 뜻을 모르고 아예 다른 글자라 생각하고 접근하는 경우도 많다고 얘기한다. 그래서 이렇게 따져가며 고민할 시간에 그냥 단어나 더  외우라는 구박을 종종 듣곤 하지만, 이렇게 기존에 알고 있는 한국 한자와 비교하는 것을 안 하고 순수하게 한자를 바라보기가 개인적으로는 무척 힘든 거 같다. 

 

posted by 자유로운설탕
2025. 7. 24. 22:12 일본어와 중국어

이번엔 외국어 표기에 대해서 얘기해 보자. 아마도 아는 범위 상 거의 영어에 대한 얘기일 거 같긴하다.

우선 우리말인 한국어는 외래어라고 분류하긴 하지만 요즘은 그런 공식적인 경계가 조금 허물어진 느낌이다. 다르게 생각하는 입장도 있을 수 있겠지만 개인적으로는 어떤 영어든 뭐 한국 발음으로 바꾸어 쓰고, 그걸 상대가 알아들을 수 있다면 그게 외국어의 한글 표기이지 않을까 하는 생각이 든다.

특히 단어의 경우는 더더욱 그래서 예를 들어, "이 문제는 델리케이트(delicate)한 측면이 있고, 크리티컬(critical)한 결과를 가져올 수 있으니 조심스럽게 어프로치(approach) 해보자" 라고 얘기한다면 국어의 순화를 중요하게 생각하는 입장에서는 무슨 근본 없는 표현이냐고 볼 수도 있겠지만(이 문제는 미묘한 측면이 있고, 중대하고 심각한 결과를 가져올 수 있으니 조심스럽게 접근해 보자), 많은 전문 분야(특히 IT 쪽을 생각해 보면 많이 그렇다)에서 어쩔 수 없이 외국어를 많이 섞어 쓰고 있다.

한국어의 "귀엽다"는 표현과, 일본어의 "可愛い(かわいい, 카와이-)" 하는 표현과, 중국어의 "可爱(kě'ài, 커아이)하는 표현은 일-중 두 나라의 경우는 한자가 같은데도 불구하고 무언가 서로 비슷하면서도 다른 느낌이 있다. 또한 한 언어의 표현으로는 엄청 길게 설명해야 되는 표현도 다른 언어에서는 하나의 짧은 단어나 사자성어로 함축이 되기도 하고, 그 반대로 장황해 지기도 하는 것 같다.

  • 自生自灭 (zìshēngzìmiè, 즈 셩 즈 미에) - 스스로 생겨나고 스스로 사라질 정도로 신경을 안 쓰고 내버려 두다.
  • 切ない (せつない, 세츠 나이) - 무언가 이루어지지 못하는 것에 대한 가슴 아프고, 안타깝고 애틋함에 대한 표현

하나 하나의 단어와 발음에는 특정한 나라 사람들의 감정이나 느낌이 시간과 엉켜서 압축되어 담겨져 있다고 생각한다. 특히 노래 가사 들을 보다 보면 이 가사는 다른 언어로는 이 느낌을 표현할 수 없겠구나 라는 생각이 드는 경우가 가끔 있기도 하고, 또한 게임 쪽에서는 농담 반 진담 반으로 게임 계의 라틴어가 일본어라고 하 듯 각 언어가 표현하는 고유의 느낌과 외국인 입장에서의 환상 비슷한 부분이 있다.



또한 우리말로 대체하려다 보면 이미 단어의 형태로 결합하여 뜻이 제한되어 버린 한자를 다시 쪼개서 결합하거나 순수 우리말로 표현해야 하는데, 이것도 좀 결이 잘 안 맞는 경우가 많다고 본다(레고의 부품이 다양하지 않은데 이걸로 뭔가를 만드는 느낌이라고 할까?).

인터넷을 찾아보면 아래와 같은 우리말로 표현하는 시도가 실패했다고 하는데, 원어의 느낌을 떠올려 보면, 틀린 표현은 아니지만 원래의 그 느낌을 충분히 표현하진 못한다는 생각이 든다. 

  • 참살이 (Well-being)
  • 맨손 음식 (Finger food)

뭐 그래서 적절한 외국어를 가져다 쓰는 게 어찌 보면 다른 나라에서 오랫동안 키워온 맘에 드는 생각의 파편들을 주워와 사용하는 거라고 생각해 보는 것도 어떨까 싶다. 그렇게 되면 외국어를 공부하는 부분에 좀 더 다른 의미도 생기고 말이다.



일본어는 아마 오래전부터 한자의 주석을 가타카나로 표시해 왔기 때문에 외국어도 자연스럽게 가타카나로 표기하지 않았나 싶긴 하다. 그래서 외국어를 구분하기가 너무 쉽다. 물론 강조어나 의성어/의태어도 가타카나로 표현하지만 영어 표현이 압도적으로 많다. 다만 일본어를 배우는 다른 나라 사람 입장에서는 히라가나의 46개의 글자를 다시 또 가타카나 라는 다른 글자로 외어야 하는 1.5배 정도의 부담이 생기고, 또 가타카나는 특정한 분야에 많이 퍼져있어 배우는 루트에 따라서 정말 늦게 나 본격적으로 경험하게 될 수도 있다. 대부분의 나라가 그렇겠지만 순수 문학으로 갈 수록 점점 보기 힘들어 지고, 반대로 기술이나 게임, 패션 같은 쪽으로 갈 수록 점점 많아질 것이다. 좀 있어 보이는 측면을 찾아보려면 의학 드라마나 경제 방송의 대화를 보면 될 거고 말이다.

그래서 일반적으로 학교, 학원을 통해 모범적인 방식으로 공부하거나, 본인의 예능이나 게임 등 특정 분야에 관심이 전혀 없다면 아마 가타카나가 공부하는 과정에서 많이 노출될 가능성이 낮아지게 되어, 나중에 어느 정도 한자와 히라가나를 잘 읽고 쓰면서도 가타카나가 나오면 아 이게 뭐였지 하는 당황스런 상황에 놓이게 될 가능성이 높다. 그런데 일반적으로 일본어는 취미가 공부를 이끄는 경우도 많은 것 같아서 오히려 이런 경우가 드문 것 같기도 하다.



우선 게임에서 자주 만나는 가타카나를 보자.

  • スタート (스타-토, Start, 스타트)
  • セーブ (세-부, Save, 세이브)
  • ロード (로-도, Load, 로드)
  • エネミー (에네미-, Enemy, 에너미)
  • メイジ (메이지, Mage, 메이지)
  • クエスト (퀘스토, Quest, 퀘스트)
  • ファイアボール (화이아보-루, Fireball, 파이어볼)
  • ライトニング (라이토닝구, Lightning, 라이트닝)
  • ヒール (히-루, Heal, 힐)

요즘 같이 글로벌 시장을 목표로 다국어 번역과 음성을 기본으로 지원하는 게임이 많아진 환경에서는 이해가 안 될 수도 있겠지만, 위를 보면 이걸 읽지 못하면 일본어로만 이루어진 게임을 하긴 힘들겠구나 라는 생각이 들지 않나 싶다. 그래서 예전에 수많은 1세대 게임 회사 사람들이 게임(특히 RPG)을 하기 위해 일본어를 사전을 찾아가며 열심히 노력할 수 밖에 없었던 상황도 있었었다.



그 다음엔 패션 쪽을 몇 개 봐보자 

  • コート (코-토, Coat, 코트)
  • スカート (스카-토, Skirt, 스커트)
  • Tシャツ (T샤츠, T-shirt, 티셔츠)
  • シンプル (신푸르, Simple, 심플)
  • オーバーサイズ (오-바-사이즈, Oversize, 오버사이즈)

이 쪽도 뭐 우리나라랑 비슷하게 여러 영어 단어들을 자연스럽게 가져다 쓴다. 



IT 쪽도 함 봐보자

  • ノートパソコン (노-토파소콘, Notebook PC, 노트북 컴퓨터)
  • キーボード (키-보-도, Keyboard, 키보드)
  • マウス (마우스, Mouse, 마우스)
  • プログラム (프로그라무, Program, 프로그램)
  • システム (시스테무, System, 시스템)
  • パスワード (파스와-도, Password, 패스워드)
  • データ (데-타, Data, 데이터)

우리나라 사람 입장에서 보면 뭔가 발음이 하나 빠져있는 거 같은 어색함이 있지만 양 쪽 다 각자 들리는 발음으로 최대한 비슷하게 표현한 부분이고, 외국 사람 입장에서는 어차피 비슷비슷 한 발음이라고 하니 그러려니 하자. 일단은 대충 비슷하게 매칭 되는 듯한 느낌이 있다.



마지막으로 경제 쪽을 봐보자.

  • マーケット (마-켓토, Market, 마켓)
  • ビジネス (비지네스, Business, 비즈니스)
  • コンシューマー (콘슈-마-, Consumer, 컨슈머)
  • キャッシュフロー (캿슈후로-, Cash Flow, 캐시 플로우)
  • ファンド (환도, Fund, 펀드)
  • トレンド (토렌도, Trend, 트렌드)



개인적으로는 일본어 쪽이 좀 더 영어 단어를 적극적으로 가져다 쓴다고 보는데, 최근 (어색해서 그랬는지) 한자 표현으로 바뀌었지만 현금 없는 버스 안에 붙여 있는 포스터의 문구를 보면 아래와 같은 표현이 있었다(구글 이미지를 찾아보면 둘 다 있으니 궁금하시면 검색을...)

  • 当バスはキャッシュレスに運営され,
  • 当(토우, 이)バス(바스, 버스)は(와, 는)キャッシュレス(캿슈레스, cashless)に(니, 로)運営(운에이, 운영)され(사레, 사역형),
  • 이 버스는 현금 없이(cashless) 운영 되어,

이런 걸 보면 우리나라 보다는 좀 더 적극적으로 일본 쪽이 영어를 일상에 적용하여 사용하는 것 같긴 하다.



중국어는 영어를 어색한 한자로 바꾸어 쓴다는 식의 인식을 받기도 하지만, 그건 아마도 순수한 자국어를 최대한 지키려 하는 정부의 문화 정책이 영향을 미치지 않을까 싶다. 그리고 문화에 보수적인 사람들이 영향을 크게 줄 수 있는 환경이라면, 우리말 지키기 운동 같은 강한 흐름이 주도권을 가지면서 그렇게 된 게 아닌가 싶다. 뭐 하지만 세상일이 그렇듯이 항상 절대적으로 옳거나 틀린 건 없을 테니 그냥 현재 상태가 그렇다고 보면 될 것 같다. 그리고 그쪽에서는 한자와 같이 숨 쉬며 살기 때문에 앞에서 얘기했듯이 한자를 조합해 외국어를 표현하는 부분이 우리가 느끼듯이 딱딱하게 느껴지지 않을 수도 있어 보인다. 우리나라나 일본이 한글을 조합해 영어를 만들면서도 자연스럽게 느끼듯 말이다.



우리나라의 경우도 중국보다 수는 적은 듯 하지만 현재는 많이 안 쓰이거나, 비슷하게 영어와 혼용해 쓰는 한자 말이나 순수 우리 말들이 꽤 존재하는 듯 하다.

  • 昇降機(승강기 vs 엘레베이터)
  • 管絃樂團(관현악단 vs 오케스트라)
  • 畫廊(화랑 vs 갤러리)
  • 房(방 vs 룸)
  • 내려받기 vs 다운로드
  • 누리사랑방 vs 블로그

이렇게 변화하게 되기 까지 꽤 많은 시간이 흘렀다는 것을 생각해 보면, 몇 십 년 후의 세상일은 역시 모를 것 같다.



우선 중국에 진출한 외국 기업의 상표를 중국어로 표현한 부분을 보자. 많이 알려 지듯이 브랜드의 독특함을 비슷하게 표현하면서 한자에 브랜드와 어울리는 뜻을 담으려고 했다고 이해는 하지만, 외국 사람 입장에서는 좀 희화화된 예시들로 받아들이는 경향이 있어 보인다.

  • 可口可乐 (kěkǒu kělè, 커코우 커러, Coca-Cola, 코카콜라, 입에 맞고 즐겁다 라는 의미가 있다)
  • 优衣库 (Yōuyīkù, 요우 이 쿠, Uniqlo, 유니클로, 우수한 옷 창고라는 의미가 된다)
  • 麦当劳 (Màidāngláo, 마이 당 라오, McDonald', 맥도날드, 이건 뜻보다는 음만 중시한 표현이라고 평가된다고 한다)



단순히 발음을 가차 한 표현들도 있다고 한다. 이 경우는 한자에 크게 뜻은 없다. 아예 해당 발음을 위한 한자를 새로 만들기도 하고 말이다.

  • 咖啡 (kāfēi, 카페이, Coffee, 커피)
  • 巧克力 (qiǎokèlì, 치아오커리, Chocolate, 초콜릿)
  • 比萨 (bǐsà, 피사, Pizza, 피자)
  • 沙发 (shāfā, 샤파, Sofa, 소파)



여기까지는 뭔가 중국어로 영어를 표기하려 하는구나 라는 생각이 드는데, 좀 더 전문적인 분야로 가면 오히려 영어 발음을 느낄 수 있는 단어들은 점점 사라지게 된다. 

IT 쪽을 보면

  • 软件 (ruǎnjiàn, 롼 지엔, Software, 부드러운 부품, 소프트웨어)
  • 硬件 (yìngjiàn, 잉 지엔, Hardware, 딱딱한 부품, 하드웨어)
  • 电脑 (diànnǎo, 디엔 나오, Computer, 전기 뇌, 컴퓨터)
  • 网站 (wǎngzhàn, 왕 짠, Website, 그물같은 세상의 역, 웹사이트)
  • 互联网 (hùliánwǎng, 후 리엔 왕, Internet, 상호 연결된 그물, 인터넷)
  • 代码 (dàimǎ, 따이 마, Code, 대신하는 부호, 코드)
  • 大数据 (dàshùjù, 따 수 쥐, Big Data, 큰 숫자, 큰 데이터, 빅데이터)



패션도 그렇다. 

  • 鞋子 (xiézi, 시에 즈, Shoes, 슈즈)
  • 包 (bāo, 빠오, Bag, 뭔가 감싸는 것, 발음도 은근 비슷하다, 백)
  • 品牌 (pǐnpái, 핀 파이, Brand, 물건의 (품질을 나타내는) 명패, 브랜드)
  • 模特 (mótè, 모 터 Model, 특별한 본보기, 발음도 좀 비슷하게 하려 한듯 하다, 모델)



경제 쪽도 애매해서

  • 市场 (shìchǎng, 스 창, Market, 시장, 마켓)
  • 风险 (fēngxiǎn, 펑 시엔, Risk, 위험, 리스크)
  • 成本 (chéngběn, 청 뻔, Cost, 이루는데 드는 본전, 코스트)
  • 基金 (jījīn, 지 진 Fund, 기반이 되는 돈, 펀드)
  • 比特币 (Bǐtèbì, 비 터 비, Bitcoin, 비트 코인)



한국인 입장에서 좀 헷갈리는 부분은 저 한자 단어들이 진짜 영어를 나타내기 위해 만들어진 새로운 단어인지, 원래 사용하던 일반적인 단어들이 영어의 의미까지 포함하게 된 건 지를 직관적으로 알기 힘들다는 것이다. 그래서 드라마 같은 걸 보다 보면 AI, NPC 같은 단어를 영어로 발음하면서, 실제로 자막의 경우는 아래와 같은 한자 단어로 나오는 경우를 보면 이게 뭐지 하는 혼란이 생긴다.

  • 人工智能 (réngōng zhìnéng, 런꽁 쯔넝, 인공 지능, AI)
  • 非玩家角色 (fēi wánjiā juésè, 페이 완지아 줴써, NPC<non-playable character>)
  • 非(아니다), 玩家(노는사람->플레이어), 角色(배역->캐릭터)



실제 특정 분야에서 중국어로 일을 하는 사람들은 경험 상 자연스럽게 습득이 되겠지만, 그것도 결국은 그 분야의 분위기에 따라서 영어로 발음하거나 중국어로 발음 하는 등 상황이 다를 것도 같아서, 이 부분도 여러 매체를 통해서 분위기를 봐가면서 감을 획득할 수 밖에 없는 부분 같다. 이 쪽은 충분히 확신을 가질 만큼 직-간접 경험을 못해봤기 때문에 이런 것이 아닐까 추측을 해 보면서 마무리를 하려 한다.

 

- Fin -

posted by 자유로운설탕
2025. 7. 15. 19:31 일본어와 중국어

이번엔 조금 자신은 없긴 하지만 기존 우리가 익숙한 언어에 비교해서 두 언어의 어순에 대해서 얘기해 보려 한다.

우선 일본어는 우리말과 두 개의 언어가 어순이 이렇게 맞을 수 있을까 할 정도로 어순이 같다고 볼 수 있다. 기본적인 어순도 같지만 동사를 활용하는 여러 변화 측면이라든지, 말의 끄트머리 같은 부분이 다르지만 같은 성격을 가졌다는 느낌을 많이 가진다. 물론 어휘나 세부적인 조사 등의 뉘앙스는 다르다고 할 수 있지만 전체적인 문장의 흐름 면에서는 정말 많이 닮아있다.

일본 영화 제목인 "나는 내일, 어제의 너와 만난다"의 원 제목을 보면

  • ぼくは明日、昨日のきみとデートする
  • ぼく(보쿠, 나) は(와, 는) 明日(아시타, 내일), 昨日(키노우, 어제) の(노, 의) きみ(키미, 너) と(토, 와) デート(데-토, 데이트) する(쓰루, 한다)

로 대응되는 조사까지도 1:1 매칭이 된다.


이번에는 pretender 라는 노래의 첫 소절을 보면 

  • 君とのラブストーリー
  • それは予想通り
  • いざ始まればひとり芝居だ
  • 君(키미, 너)と(토, 와)の(노, 의)ラブストーリー(라브스토-리-, 러브스토리)
  • それ(소레, 그것)は(와, 은)予想(요소우, 예상)通り(-도오리, 대로)
  • いざ(이자, 막상)始まれ(始まる, 하지마[루], 시작하다),ば(바, ~하니)ひとり(히토리, 혼자)芝居(시바이, 연극), だ(다, ~다)

한국어만 빼내 보면

  • 너와의 러브 스토리, 그것은 예상대로, 막상 시작해보니 혼자만의 연극이야

 

 

여기다 동사나 형용사의 변화를 보게 되면 외국 사람이 한국어 할 때 힘들다고 하는 부분과 비슷해 보이지 않나 싶다.

  • 食べる(타베루, 먹다) 의 동사로 시작해 보면
  • 食べます(타베+마스、먹습니다) / 食べながら(타베+나가라, 먹으면서)、
  • 食べなさい(타베+나사이, 먹으세요/먹어라) / 食べて(타베+테, 먹고/먹어서/먹어)、食べた(타베+타, 먹었다)
  • 食べない(타베+나이, 먹지 않다) / 食べなかった(타베+나깟다, 먹지 않았다)
  • 食べよう(타베+요우, 먹자/먹으려고 한다)

한국어의 먹고, 먹어서, 먹지만, 먹어봐, 먹으려고, 먹을 수 없는, 먹었다 등등의 변화 느낌과 비슷하다.


小さい(ちいさい, 치이사이, 작다) 의 형용사로 시작해 보면

  • 小さいだろう(치이사이+다로우, 작을 것이다) / 小さかった(치이사+깠다, 작았었다)
  • 小さくない(치이사+쿠나이, 작지 않다) / 小さく(치이사+쿠, 작게)
  • 小さくて(치아사+쿠테, 작고/작아서) / 小さい人(치이사이+히토, 작은 사람)
  • 小さければ(치이사+케레바, 작다면/작으면)

역시 마찬가지로 작은, 작아서, 작다면, 작았었다 등등의 한국어 어미 활용과 비슷한 느낌이 있다.


그래서 일본어의 경우 우리나라의 말과 비슷하게 "그게 맞는다고 생각할지도 모른다는 바가 맞지 않을지도 모르겠다고는 할 수 없겠지만" 같은 약간 괴랄한 표현이 가능하고 시험에도 요런 부분에 익숙한 지를 보기 위해서 종종 이런 꼬인 표현들이 나오는 듯 싶다.

물론 우리말에 해당 하는 자연스럽고 우리가 보기에는 미묘해 보이는 일본어 표현들을 선택하는 것은 여전히 어려운 일인 건 맞긴 하겠지만, 어순이나 문법 요소들이 같다는 것은 무조건 언어를 익히는 데 장점이 되는 게 아닌가 싶다. 그 반대 급부로 어순을 맞추기 쉽기 때문에 자신이 좀 더 저쪽 사람들이 느끼기 보다는 잘한다고 생각하게 되는 착각에 빠질 가능성도 조금은 높지 않을 까 하는 생각도 들기도 한다^^;

 



다음으로 중국어는 처음에 배울 때 동사가 먼저 나오는 특성 상 영어랑 같은가 보다 했다가 돌을 맞았던 경우라고 볼 수 있다. 아직도 말할 때는 많이 헷갈려서 회화 연습을 하다가 초급 때 배우던 부분도 모르냐고 많이 야단을 맞고 소심해 지는 편이다. 우선 중국어와 영어와 비슷하다 생각할 수 있는 부분 부터 얘기해 보자.

우선 많이 나오는 얘기 중 하나는 영어, 스페인어, 프랑스어 등의 언어에서 보이는 주어 + 동사 + 목적어 순서이다(이번에 궁금해서 찾다 보니 저 언어들도 생각보다 형용사나 동사의 변화 등이 틀리고 그런 듯해서, 저 나라 사람들은 앞의 우리나라 사람의 입장에서의 일본어 처럼 어순이 거저 먹기는 아니겠구나 싶었다. 뭐 물론 동사들의 어원 같은 면에서는 이쪽의 한자의 익숙함 같이 유리하겠지만 말이다). 

  • I like her
  • 我喜欢她。(Wǒ xǐ‧huan tā, 워 시환 타, 나는 그녀를 좋아한다)
  • 我(I) 喜欢(like) 她(her)

 

 

개인적인 느낌으로는 동사가 먼저 나오는 언어 들이 자신을 표현하는데에 있어서는 조금 더 자기 중심적인 포지션을 가진 것은 아닌가 싶기는 한다. 언어가 만들어지는 순서를 보면 

  • "I like..., 我喜欢" 인 경우는 이미 내가 무언가를 좋아 한다는 것을 알지만 무엇인지는 아직 모르는 상태이고,
  • "나는 그녀를..." 이라고 하면 나와 그녀는 어떤 배경에 그려졌지만 둘과의 관계는 알수 없는 상태이다.

 


여튼 이래서 중국어가 어순이 어렵나 하는 생각을 한다는 핑계일 뿐이고, 또 비슷한 어순이라고 생각이 드는 부분은 사역 같은 부분이다. 아래의 "나는 그녀를 숙제를 하게 했다" 라는 문장의 예를 보자.

  • I made her do her homework.
  • 我让她做作业。(Wǒ ràng tā zuò zuòyè, 워 랑 타 쭈어 쭈어예)
  • 我(I) 让(made-사역동사) 她(her) 做(do) 作业(homework)

여기까지도 일치해 보인다. 여기까지 보면 중국 사람들이 영어할 때 참 좋을 거 같고(앞의 2편에서 얘기했던 실제 발음은 좀 다르다곤 하지만 p,f 나 z, j 발음, r, l 발음 등의 구분을 하는 부분도 있고), 반대로 영어를 하는 사람들도 중국어를 할때 참 좋을 거 같긴 하지만 다른 문장 요소가 들어가면 이제부터 헷갈리기 시작한다.


우선은 의문문을 보자. "너 언제 가?" 라는 표현을 보자.

  • When are you going?
  • 你什么时候走?(Nǐ shénme shíhou zǒu?, 니 션머 스호우 조우?)
  • 你(you) 什么时候(when) 走(go, leaving)

여기서 보면 완전 어순이 어긋 나는 것을 볼수 있다.

 

근데 여기서 다시 보게 되면, 이 경우는 한국어랑 어순이 같다--;

  • 你(너) 什么时候(언제) 走(가)?

지금도 헷갈리지만 이래서 처음에 너무 헷갈렸다.


그럼 또 다른 예제로 가서 날짜와 시간, 장소를 넣어보자.
"나는 7월 7일 11시에 집에 있을 것이다."

  • I will be home on July 7th at 11 AM.
  • 我7月7号上午11点会在家。(Wǒ qīyuè qīhào shàngwǔ shíyīdiǎn huì zài jiā, 워 치웨 치하오 샹우 스이디엔 훼이 짜이 지아)
  • 我(I) 7月 7号(July 7th) 上午 11点(at 11 AM) 会(will) 在家(be home)。

여기서 보면 on, at 같은 문장 요소도 생략되어도 괜찮고, 어순도 완전 헷갈리게 된다.

 

여기서는 한국어와 비슷해지긴 하지만 또 동사나 조동사의 위치 관계 때문에 또 다르게 된다.

  • 我(나는) 7月7号(7월 7일) 上午 11点(오전 11시) 会(있을 것이다) 在家(집에)。

 

그리고 한국어는 아무래도 날짜나 시간 같은 건 사실 좀 위치가 어느 정도 왔다 갔다 할 수도 있다.
나는 7월7일 11시에 집에 있을 것이다 -> 나는 집에 7월7일 11시에 있을 것이다. -> 7월7일 11시에 나는 집에 있을 것이다.
라고 해도 딱히 틀리다고 얘기할 순 없다고 본다.


과거나 지속의 표현도 다르다. 
"나는 게임을 하고 있다" 라는 지속 표현을 보자

  • I am playing a game.
  • 我在玩游戏。(Wǒ zài wán yóuxì, 워 짜이 완 요우시)
  • 我(I) 在(-ing) 玩(play) 游戏(game)

a 같은 부정 관사 같은 거는 없고, 동사의 변형이나 어미의 변화를 이용해서 과거나 진행을 표현하는 영어나 한국어와는 달리 在 라는 말 뒤에 동사를 쓰면 진행의 의미가 된다.

 


이번엔 과거 표현인 "나는 빵을 먹었다" 라는 표현을 보자

  • I ate bread.
  • 我吃了面包。(Wǒ chī le miànbāo, 워 츠 러 미엔빠오)
  • 我(I) 吃(eat) 了(eat 동사의 과거표현) 面包(bread)

동사 뒤에 了를 붙여 과거로 만드는 것은 조금 억지를 부려 생각하면 한국어에서 과거형 어미를 붙이는 거랑(먹다 -> 먹었다) 비슷하다고 볼 수도 있다. 물론 동사가 먼저 나오는 특성 땜에 한국어와는 순서가 완전히 다르긴 하다.


조동사 같은 건 뉘앙스는 약간 틀리지만 또 비슷한 경우가 많다

  • I will protect you.
  • 我会保护你的(Wǒ huì bǎohù nǐ de, 워 훼이 빠오후 니 더)
  • 我(I) 会(will) 保护(protect) 你(you) 的(이건 없어도 되지만 문장을 좀더 자연스럽게 만들어 준다고 한다)

한국어에서는 -ㄹ 같은 걸 붙여서 "하다"를 "할 것이다" 로 바꾼다. 앞에 얘기했듯이 일본어도 비슷하고 말이다.


영어의 전치사와 비슷한 느낌을 주는 결과보어라는 문법도 있다. 이것도 한국어를 쓰는 입장에서 입에 잘 붙지 않는 표현이다.

  • I ate up the cake.
  • 我把蛋糕吃完了。(Wǒ bǎ dàngāo chī wán le, 워 빠 단까오 츠 완 러)
  • 我(I) 把(사역 make 정도의 느낌) 蛋糕(cake) 吃(eat) 完(up)了(eat 의 과거)

게다가 이런 경우는 관례 적으로 내가 해당 케이크를 다 먹어 처리했다는 뉘앙스로 "把" 를 붙인 사역형으로 표현한다.
(完 같은 결과 보어도 그렇지만, 드라마를 보면 대화에 한국 사람 입장에서는 붙여야 하나 싶은 저 把를 붙인 표현도 정말 많다)


이런 면에서 보면 중국어는 영어를 기본적으로 많이 배워 문법에 익숙한 한국인 입장에서 영어 같기도 하면서, 또 많은 부분에서 한국어 같기도 해서 처음에 많이 어순이 헷갈리는 것 같기도 하다. 반대로 생각하면 영어도 잘하고 한국말도 잘하는 사람 입장에서는 두 언어에 유사한 부분을 잘 조합해서 습득하면 쉽게 이해할 수 있다고 생각할 수도 있고 말이다. 강사 분은 중국어가 한국어랑 엄청 문장 구조가 비슷하다고 느낀다고 하는 데 개인적으로 볼 땐 그냥 그 사람은 언어에 감이 뛰어난 것 같긴 하다^^;

또한 중국어는 그냥 우리가 생각하는 일반 문장보다 앞의 把 문이라든지, 조동사 라든지, 是~的 구문이라든지, 결과 보어 같은 요소를 이용한 관례 적인 표현을 엄청 많이 사용 하기 때문에, 그냥 한국어를 문장으로 기반으로 만들면 영어, 일본어와 똑같이 어색한 표현이 되어버리는 거 같다. 여하튼 너무 어려운 부분이긴 한데, 많이 경험하고 자연스러운 표현을 익히는 수 밖에 없지 않나 싶다. 

 

그럼 위에서 얘기한 是~的의 예제를 하나 보면서 마무리를 하자.

  • "나는 <어제> 왔다" 라는 어제를 강조하는 의미가 된다.
  • 我是昨天来的。(Wǒ shì zuótiān lái de, 워 스 쭈어티엔 라이더)
  • 我(나) 是(是~的 구문) 昨天(어제) 来(오다) 的(是~的 구문)


그리고 글을 쓰면서 서로 표현하기 어려운 발음이 있는 현실 상, 한글 발음을 적는 게 좀 맞나 싶긴 한데, 그래도 중국어나 일본어를 잘 모르는 분들 상황에서는 외국어만 쓰는 것보다 한글 발음이 있는 게 뭔가 더 도움을 줄듯해서 넣으니 조금 맘에 안 드셔도 이해해 주시길 바란다. 

- Fin - 

posted by 자유로운설탕
2025. 7. 6. 16:30 일본어와 중국어

한국어는 한글이 만들어지기 전까진 이두 문자라는 방식으로 한자어를 가차해 우리말을 표현했다고 한다. 궁금해서 찾아보니 지금 한글 같이 음과 1:1로 매칭되는 요소는 아니고, 한자의 뜻과 일부 한자를 문법적 요소로 차용해서 섞어 쓰는 지금 보면 묘한 표기인 것 같다. 어찌보면 일본어에서 한자에 고유 글자인 히라가나가 붙은 것과도 비슷하다. 
  예) 王是(왕 왕, 이 시) - 왕이다, 見古(볼 견, 옛 고) - 보고

위를 표현을 보면 기본적으로 한자 자체를 모르면 한자를 이용해 글을 적거나 반대로 해당 표기에서 우리말의 뜻을 알아내는 건 무척 힘들었을 것으로 생각되며, 그런 측면에서 "나라의 말이 중국과 달라 문자와 서로 통하지 아니하니" 라는 훈민정음 반포문의 시작 부분이 공감이 가는 듯 하다. 또 한편으로 처음 한글을 도입하려 했을때, 얼마나 기존 표기에 익숙해진 나라 전체의 사람들의 반대가 심했을까 싶다. 

또한 이후에도 바로 한글만 사용하진 않고 많은 신문 등에서도 한자를 한글이랑 한참 섞어쓰는 시기도 오래 있었다. 여러 과정을 거쳐 90년대 정도 부터 순수 한글 표기를 했다고 하는데, 어찌보면 이러한 표현 방식은 이두 표기와 현실의 한글이 타협했던 느낌같기도 하다.
  예) 護國의 烈士를 기리다 - 호국의 열사를 기리다

지금 시대에 와선 크게 의미없어 보이지만 그때는 한자를 읽고 쓸수 있는게 글을 읽을 수 있는 교양의 기준을 나타내는 지표이던 때도 있던거 같다(뭐 이 기준은 항상 시대에 따라 달라지는 거지만 말이다).


일본어는 한자, 그리고 고유 글자인 히라가나, 그리고 애니메이션이나 게임, 노래 등 오타쿠 입문이 아닌 순수한 상태로 일본어를 공부할때 제일 어려워할 가능성이 높은 카타카나 라는 히라가나와 음은 같고 모양이 틀린 고유의 글자가 섞여 사용된다. 
  히라나가 예) あ い う え お (아 이 우 에 오)
  카타가나 예) ア イ ウ エ オ (아 이 우 에 오)

개인적으로는 일본어에서의 한자는 익숙하다는 가정하에 글의 가독성을 높여준다고 생각하지만 이것 또한 우리나라의 한자 섞어 쓰기가 어느 시점에 사라진거 같이 편견일 수도 있을거 같다
한자 버전) 世界で最も美しい顔100人をもしも、すっぴんで選んだらこうなる
히라가나 버전)せかいでもっともうつくしいかお100ひとをもしも、すっぴんでえらんだらこうなる!
  해석) 세계에서 제일 아름다운 얼굴을 가진 100인을 만약, 화장을 안한 얼굴로 선택을 한다면 이렇게 된다.
  世界(세계), 最(가장 최), 美(아름다울 미), 顔(낯 안) , 人(사람 인), 選(가릴 선)

또한 일부 일본에서만 사용하는 고유한자가 있는데 수가 작고 약간 뜻만 다를뿐 모양이 비슷해서 특별히 공부할 때 큰 영향은 주지 않는듯 싶다.
 예) 働 (はたらく, 하타라쿠, 일하다) - 사람(人)이 움직이다(動, 움직일 동)

히라가나는 우리가 가장 일본어를 보면서 많이 볼수 있는 글자이며 단어 뿐만 아니라 문장 성분은 모두 히라가나로 표시한다. 우리말의 한글 포지션 이라고 생각하면 맞을 듯한다. 얘기로는 한자의 초서에서 시작되어 주로 문학 작품이나 편지에서 쓰다가 현대는 일본어의 중심이 된것 같다.
 예) 学校に行く(がっこう に  いく, 갓코우 니 이쿠, 학교 에 가다)

카타카나는 불경의 주석이나 한문의 표기 등에 썻다고 하며, 일본어에서는 영어 단어를 표기하거나, 의태어, 의성어, 특정한 뉘앙스를 강조할때 쓰는 편이라 한다. 마지막 뉘앙스 강조 부분은 감이 없어 생략한다.
  영어) ナイト(나이토, night, 밤)
  의성어) ニャーニャー (냐- 냐-, 고양이 울음)
  의태어) ドキドキ (도키도키, 두근두근)

발음은 자신 없어서 얘기하긴 좀 애매하지만, 받침을 거의 보기 힘들어서 우리말 보다는 한음절 더 긴 느낌들이 있고, 몇 가지 특이한 부분은 아래와 같다. 나머진 열심히 반복해 외는 수 밖에 --;
1) 원래 글자로도 쓰고 조그매져서 받침으로도 쓰는 경우(つくえ, 츠쿠에, 책상 vs がっこう, 갓 코우, 학교)
2) 위에 더해 유일한 받침 같은 느낌의 글자 ん (みんな, 미 ㄴ 나, 민나)
2) 점이 두개(゛) 붙거나, 작은 동그라미(゜)가 하나 붙어 발음을 바꾸는 애들이 있음(は-ば-ぱ, 하-바-파)


마지막으로 중국어를 보자. 중국어는 한자 밖에 없긴하지만 번체(정체), 간체, 이체 라고 구분되기는 한다. 번체나 정체는 아마도 각자의 입장에 따른 차이가 있을 것 같은데, 훈민정음의 취지 비슷하게 사람들이 읽고 쓰기 힘들었던 복잡했던 번체(繁體, 번성할 번, 몸 체)를 간체(簡體, 간략할 간)로 간단하게 만들어, 읽고 쓸수 있는 사람을 늘인다는 좋은 의미의 입장을 옹호하기 위한 측면이 있다고 보이고, 정체(正體, 바를 정)는 반대로 기존의 전통 한자의 모양을 지키고 쓰는 게 맞다고 생각하는 홍콩, 대만, 마카오 등의 철학이 부딪치는 부분이라고 이해하면 될 듯 하다.  

그래서 번체(정체)는 한국, 홍콩, 대만, 마카오, 일본 등 에서 사용하는 오래전 부터의 중국한자라고 볼수 있고, 간체는 중국에서 기존한자를 간략하게 축약한 형태의 한자라고 보면 된다. 그리고 이체는 한자의 사투리 글자 정도로 보면 되지 않을까 싶다. 현재는 우리나라에서는 중국한자라고 하면 당연히 간체로 생각하지만 역사적인 이유로 예전에는 학교에서 대만어 기준으로 중국어를 교육하던 시기에는 번체로 배우는 경우도 있었다.

회화 측면에서는 대만은 번체(정체)를 쓰지만, 표준 말은 북경을 기반으로 한 방언이라서 어조나 일부 단어들에 차이가 있지만 중국의 표준어인 북경어와 거의 통한 다고 한다(별개로 대만 쪽 사투리도 있다고 한다). 반대로 홍콩이나 광동성, 마카오 등지에서는 광동어라는 다른 계통의 회화를 사용해서, 표현 자체가 많이 다르다고 한다(궁금하면 유투브를 찾아보자). 간체를 쓰더라도 정상적으로 교육은 받은 경우라면 번체를 보통 읽을 수는 있다고 하고, 다른 지역 지역들도 표준어 또한 배울 거긴 때문에 젊은 사람들은 다들 아마도 공통적으로 북경어가 통하지 않을까 싶기는 추측한다.

번체와 간체의 차이는 한국 사람이 중국어 공부를 시작할때 가장 눈앞에 맞이하게 되는 장벽일거 같다. 그런데 알고 보면 전혀 다른 글자가 아니라 번체를 기준으로 복잡한 한자 요소들을 좀더 간략한 요소로 대체하는 컨셉으로, 구조는 유지하면서 글자를 단순하게 만들었다고 보면 된다. 간단히 줄이거나, 고대 글자를 채용하거나 하는 등의 학자들의 견해가 적용되었다는데 예제를 보면서 대충 감을 느껴보자
  1) 요렇게 소심하게 변해 간단하게 이해가 되는 부분부터 
     語 → 语(말씀 어), 談 → 谈(말씀 담)
     銀 → 银(은 은), 銅 → 铜(구리 동)
     飲 → 饮(마실 음), 飯 → 饭(밥 반)
     門 → 门(문 문), 問 → 问(물을 문)
  2) 조금 더 과감한 애들도 있고
     開 → 开(열 개), 關 → 关(닫을 관)
     龍 → 龙(용 용), 飛 → 飞(날 비), 學 → 学(배울 학), 雲 → 云(구름 운)
  3) 두개를 하나로 간략히 합치는 경우도 많지는 않지만 있으며
     髮/發 → 发(터럭 발, 필 발)
  4) 결국 복잡한 한자에서는 위의 줄임 현상들이 겹쳐서 일어나게 된다. 
     識 → 识(알 식), 譯 → 译(번역할 역)

한자는 개인적으로 이런 특정한 부수 형태의 글자들이 2차원 형태로 배치된 거라고 보기 때문에, 작은 크기의 간체에 익숙해 지다 보면 복잡한 글자도 나중엔 4번 같이 부분 부분에 대한 치환이 일어나는 것으로 보여져 그러려니 설득이 되는 것 같다. 한국인이기 때문에 번체를 같이 공부하는 부분은 요즘 온라인 사전 보면 번체가 같이 표기되서 한번씩 간체 찾으면서 비교해 보면 더 좋을 듯 하다. 아래와 같이 일부 간단한 글자 위주로 번체랑 간체가 같은 경우도 있긴 하다(가끔 조금 복잡한 글자가 번체와 간체가 같으면 왜 안 줄였지 신기하기도 하다)
  예) 我, 鼎, 好

뭐 요즘은 스마트폰으로 글자를 쓰기 때문에 외국인 입장에서 번체나 간체나 모양만 알고 있음 쓰는 데는 큰 차이는 없는 듯한데, 다만 노안이 오기 시작하면 아무래도 획수가 작은 간체가 좀 더 눈에 잘 보이지 않을까 하는 소심한 의견을 내본다. 개인적으로 아래 한자를 처음 봤을 때 장난인가 했었다 --;
凹, 凸 (오목할 요, 볼록할 철)

마지막으로 중국어 발음은 영어와 다르긴 하겠지만 p, f 및 z, j 및 l, r 발음이 각각 있다. 또 조금 특이한 발음으로는 zh, ch, sh 과 z, c, s 발음이 구분된다. 그래서 은근 많이 틀리기도 하고 어려운 것 같다. 조금 특이한 발음 기호로는 ü 도 있다. 

그리고 성조는 높은 성조(--) 올라가는 성조(↗) 내려갔다 올라가는 성조(↘↗), 내려가는 성조(↘), 짧게 끊어지는 성조가 있다. 발음과 성조는 회화의 영역이라서 선생님께 열심히 배우자^^;

 

- Fin -
 

posted by 자유로운설탕
2025. 7. 5. 22:39 일본어와 중국어

세 나라가 모두 한자를 기반으로 사용하고 있는 편이기는 하지만, 한자의 의미를 사용하는 범위의 측면에서 보면 차이가 나는 부분이 있어 그걸 첫 글의 주제로 하려 한다.

우선 한국어는 거의 모든 한자 단어를 명사적 의미를 기준으로 사용하고 있다고 본다. 예를 들면 "행복(幸福)"이라는 단어를 기본적으로는 명사적인 느낌으로 사용하면서, "하다" 라는 어미를 붙이면 "행복하다"라는 형용사가 된다. 또는 "행복한", "행복하게" 등의 다른 조사로 다른 역할로 만들 수 도 있다. 또는 동사의 경우도 "추억(追憶)"은 하나의 명사적 의미로, "추억하다"는 동사가 된다. 어찌보면 엄청 심플하게 명사적 의미의 한자 단어 + 한국어를 붙여서 자연스럽게 어휘를 만들어 낸다. 명사가 아닌 식으로 쓰이는 경우는 "과연(果然)" 와 같은 부사 같은 단어들이 있는 듯하다.

이렇게 한글과 1:1로 매칭되는 단어의 특징 및 두개 이상의 한자를 단어로 결합해 쓰는 특성 상 일반적으로 뜻이 명확하게 제한된다고 본다. 약간의 베리에이션이 있는 경우도 사실 거의 의미상으로 아주 이상하지 않은 정도의 이질감만 있다고 본다. 그래서 "다행 행, 복 복" 와 같이 한자 하나하나의 의미를 명확하게 인지해 외우는 것이 명확한 것을 지향하는 한국어에서의 한자 단어를 이해하는데 도움을 준다고 생각한다.


일본어 같은 경우도 한 쪽 측면에서는 한국어와 비슷하다. 무리(無理, むり) 라는 단어를 가지고, 無理です(무리데스, 무리입니다) 라고 표현 할 수도 있고, 無理な(무리나, 무리한) 이라고 표현할 수도 있다. 無理な要求(무리한 요구) 같이 말이다. 여러 단어의 뜻이나 문장 성분들이 한국어랑 아주 많이 유사하기 때문에 여기까지는 큰 위화감이 느껴지진 않는다. 게다가 읽다 보면 발음 또한 받침이 거의 없는것을 빼면 은근 1:1 에 가깝게 비슷해서 어느 정도 한자 읽기가 익숙해짐 한글 한자로부터 일본 음을 유추하거나 반대로 유추하는 작업도 비교적 쉬운 편이다.

하지만 일본 한자의 다른 특징인 "음(音読み, おんよみ, 온요미) 및 뜻(訓読み, くんよみ, 쿤요미)" 으로 표현되는 부분과 만나면 차이가 시작되게 된다. 꽃을 나타내는 花(화)는 우리나라의 순수 우리말 같은 뜻을 얘기하는 히라가나 표현으로는 "はな-하나" 라고 읽히며, 花(はな, 하나, 꽃), 花火(はなび, 하나비, 불꽃놀이), 花屋(はなや, 하나야, 꽃집) 같은 단어들로 파생된다. 반대로 한자 음을 이야기하는 "か, 카" 라는 발음이 되면 花瓶(かびん, 카빈, 화병), 開花(かいか, 카이카, 꽃이 피다) 같이 표현이 된다. 또한 더 나아가면 앞의 花火(하나비, 불꽃놀이) 같은 경우는 앞(하나)은 뜻, 뒤(비)는 음이라, 음과 뜻의 발음이 하나의 한자 단어에 뒤 섞인 경우도 많다. 여기서 부터 왠지 일본어의 한자 사용에 대한 차이가 시작되지 않나 싶다.

차이가 더 확대되는 부분은 한 글자의 한자가 중심 의미를 담당하고 순수 일본어인 히라가나와 합쳐져서 한자의 뜻을 차용한 한자어와 고유어를 섞어 놓은듯한 단어를 만드는 부분이다. 위에 얘기한 우리나라의 "한자+한글" 조합과는 조금 다른 것은 보통 한 글자의 한자가 기본적인 한자의 뜻을 나타내고 히라가나 어미가 붙어서 뉘앙스를 변화 시키기 때문에 두 개의 한자로 이루어진 단어보다 뜻의 확장이 좀더 자유로워 보인다.

예를 들어 우리 나라의 明(밝을 명)으로 이루어진 단어를 보면 명확(明確), 명백(明白), 조명(照明), 명랑(明朗) 등 무언가를 밝히거나 분위기가 밝다는 의미를 단어 하나하나가 명확하게 가지고 있지만, 일본어의 경우는 明かす(あかす, 아카스) 는 비밀등을 밝히다, 밤을 밝혀 새우면서 무슨일을 하는 것을 얘기하고, 明るい (あかるい) 는 성격이나 공간이 밝다는 것을 얘기한다. 둘다 밝다는 명(明) 한자의 의미의 확장이지만 좀더 자유분망한 느낌이 있다.

하나 더 예를 들자면 우리나라에는 거의 단어로 안 쓰이는 込(혼잡할 입) 같은 경우는 込む(こむ, 코무)에서는 혼잡한 상태, 또는 무언가 (복잡하고) 깊은 상황으로 들어가는 것을 얘기하지만, 込める(こめる)는 무언가를 담거나 넣는 것을 얘기한다. 이렇게 되면 뭔가 한자가 하나의 의미로 명확한 의미가 아닌 느낌이 생기면서 일본어를 자연스럽게 표현하는 부분의 어려움이 시작되는 것 같다. 만약 위의 한자를 우리나라에서 인지하는 단일 한 뜻인 "혼잡할 입"으로만의 의미로 생각한다면 込める 같은 단어를 볼 경우 한자를 알더라도 쉽게 의미를 파악하기는 힘들게 될것이다.

 

 

마지막으로 중국어는 우리나라와 일본 같이 기댈 수 있는 다른 순수한 말이 없다. 한자 자체로 모든 조사나 접속사, 형용사, 동사, 명사 등의 역할을 다양하게 표현해 전달해야 한다. 그래서 처음에 읽을 수 있는 한자가 적은 상태에서는 문장 구조 자체가 잘 보이지 않기 때문에 한자로 이루어진 빽빽한 문장들을 보게 되면 어디서 읽기를 시작하고 끊어야 할지 막막한 느낌이 든다.

다른 측면에서 일본이나 우리나라의 한자로만 구성되고, 특히 일본의 경우 한자 발음으로만 읽히는 단어들을 보면 무언가 순수한 우리말 단어보다 더 추상적인거나 무거운 느낌으로 인식해 쓰는 경우가 많다. 그런데 중국 사람들이 우리와 똑같이 그렇게 무거운 느낌으로 한자 단어들을 사용을 한다면 아마 일상 대화들이 엄청 딱딱하고 힘들게 느껴지는 모순이 생길 수 밖에 없을것이다. 이러한 데서 단어의 무게에 대한 차이가 존재하게 된다.

예를 들어 유쾌(愉快, 즐거울 유, 쾌할 쾌)하다는 단어는 우리 말에서는 약간 (현학적으로) 통쾌 하면서 재밌다는 느낌에 가깝게 쓰긴 하지만(ex: 유쾌한 친구네. 유쾌한 대화였어), 애들이 "오늘 친구네 집에 가서 유쾌하게 놀았어" 하면 너무 표현이 점잖아져서 많이 어색한 느낌이 있다. 이 때는 "오늘 친구네 집에 가서 즐겁게 놀았어"가 뭔가 좀 더 자연스러울 것이다. 하지만 중국어로는 즐겁다는 우리말이 없기 때문에 周末愉快(zhōumò yúkuài, 쪼우모 위콰이) 라고 하면 느낌에 따라 "주말 즐겁게 보내"라는 아주 가벼운 말이 된다.

또는 원래 뜻으로는 거의 안 쓰면서 가벼운 뜻이 되는 경우도 있다. 우리나라의 법적 용어인 고소(告訴, 고할 고, 호소할 소)라는 단어는 중국어에서는 간체로 모양만 조금 다른 告诉(gàosu-까오수, 고할 고, 아뢸 소) 라는 같은 단어로 알려주다/말해주다의 가벼운 의미로 사용되거나, 조금 무겁게 되면 무언가를 시키거나 전달해 주는 의미로도 쓰인다. 告诉我(Gàosu wǒ, 까오수 워, 나에게 알려줘) 같은 표현 같이 말이다.

마치 영어 단어에서 많이 보이는 것 처럼 한자의 의미를 여러가지 뉘앙스로 확장하는 단어들도 많아서, 교대(交代, 사귈 교, 대신할 대) 같은 경우는 우리나라에서는 "교대로 OO을 하다", "그와 근무를 교대하다" 식으로 무언가가 번갈아 가거나 순서를 넘기는 의미로 쓰지만, 중국에서는 책임을 인계해 명확히 넘긴다는 의미가 좀 더 강조되거나, 누군가에게 보고 및 설명하거나, 어떤 일을 맡기거나 부탁한다는 의미가 있다. 给我一个交代!(gěi wǒ yī gè jiāodài, 게이 워 이거 찌아오따이, 나에게 설명을 해봐!) 같이 말이다. 이런 문장을 기존 한자의 뜻에 익숙한 한국인 입장에서 처음 보면 익숙한 단어 인데도 무슨 말인지 이해가 안 가고, 특히 말할 때는 더욱 선뜻 이런 표현은 나오기 힘들게 된다. 이런 차이 때문에 영어에서 콩글리쉬를 사용하는 것이 같이 한자를 잘 알고 있어도 말도 안되는 문장을 만들거나, 중국 사람은 잘 안 쓰거나 다른 의미로 쓰는 한자 단어를 섞어 쓰면서도 이상함을 모르는 상황이 되긴 한다. 뭐 이건 어느 언어나 마찬가지지만 상황과 동떨어진 뉘앙스의 어휘를 사용해 표현을 하게 되는 문제 인것 같긴하다. 이런 면에서 AI는 참 상황에 따른 적절한 어휘를 확률적으로 잘 선택하도록 훈련되어 있어 부럽긴 하다.

특히 한 글자로 된 把(bǎ, 빠, 잡을 파), 了 (le, 러, 어조사 료), 光(guāng, 꽝, 빛 광) 같은 여러 단어들에 대해서는 영어의 get, take, make 같이 다른 한자와 결합되거나 문장에서의 위치에 따라 현란하게 의미가 변하는 단어들이 많은 듯 한다. 또한 단어에 따라서 관례적으로(그쪽 사람들은 당연히 그렇게 느끼겠지만) 명사나 동사로만 쓰이는 단어도 있고, 양쪽 용도로 모두 쓰일 수 있는 단어가 있어서, 하나의 한자 단어를 조사에 따라 자유롭게 명사, 동사 등으로 전환해 쓰는 한국 사람 입장에선 많이 헷갈린다. 이 부분은 종종 사전이나 예제에서 보는 것과 실제 강사분이 느끼는 부분이 다른 경우가 많아서 뭔가 사전을 찾아가며 교정하기도 어려운 부분이다. 이 부분도 경험적으로 하나하나 상황을 겪으며 깨달는 수밖에 없는 부분 같이 보인다.

예로서 参加(cānjiā, 찬지아, 섞일 참, 더할 가, 참가하다)와 같은 단어는 한국어로는 명사와 동사 모두로 사용이 가능하지만, 중국에서는 동사 형태로만 보통 쓴다고 한다. 我参加比赛(Wǒ cānjiā bǐsài, 워 찬지아 비싸이, 나는 시합에 참가하다)같이 말이다. 또한 굳이 참가라는 명사적 표현이 필요하면 동사를 이용하는 방식으로 표현 한다고 한다. 이런 것도 참 기존 한국어의 한자를 쓰던 방법에 익숙한 입장으로는 캐치하기 힘든 부분인 것 같다.

결국 이런 부분은 어느 언어나 마찬가지 겠지만 실제 해당 나라 사람들과의 대화나 경험, 드라마나 책, 게임 같은데서 간접적으로 경험하면서 하나하나 습득할 수 밖에 없는 부분 같아서, 더 갈 길이 멀어 보이기는 한다. 특정한 경우에는 서로 다른 언어 사이에 감정이나 관념적으로 완벽히 대치되는 표현이 없는 경우도 있긴 하겠지만, 한자어로 그 수많은 사람들이 사는 환경에서의 여러 일들과 감정등을 완전하게 표현하기 때문에 중국어를 공부하는 외국인 입장에서는 애매한 영역들이 아주 많아 보인다. 특히 한국 사람들이나 일본 사람들 입장에서는 한자 단어를 많이 아는 게 시작할 때 분명히 도움이 되긴 하지만, 위의 뉘앙스 적인 차이 문제 때문에 자연스러운 중국어를 사용한다는 부분에서는 나중에는 반대로 발목을 잡는 아이러니도 있는 듯 하다.

 

- Fin -

posted by 자유로운설탕
2025. 7. 5. 22:38 일본어와 중국어

우선 이렇게 서문을 적는 이유는 어떤 배경을 가지고 두 개의 언어를 비교하려 하는지는 간단히 설명하고 글을 진행하는 게 좋을 듯 해서이다.

예전에 취미로 언어 공부를 하고 싶어서 무작정 주말에 일본어 학원을 다니게 되었다. 8~9년 정도 정말 가볍지만 그만두진 않고 다니면서, 주로 회사 다니는 지하철에서 단어장을 읽고, 게임 대사를 정리하고, 노래 가사를 외고, 일본어 책도 몇 권 읽고, 드라마도 보고 하다가 어느날 시험을 보러가 볼까 하는 생각이 문득 들어서 JLPT N2 시험을 보러 갔다 엉결겹에 붙고 말았다(학원 선생님 중에 한 분이 신기해 하면서, 저 같이 열심히 안 하는 사람도 꾸준히만 하면 N2 시험에 붙을 수 있다고 수강생 들한테 희망 섞인 얘기를 해줘야겠다고 하던 기억이 있다).

이후 왠지 일본어는 혼자 공부할 수 있을 것 같은 근거 없는 자신감이 생겨서, 중국어 학원을 비슷하게 주말에 다니기 시작했는데 한자와 성조 외는 게 처음에 스트레스가 심해서 일본어 보다는 많이 적응하기 힘들었던 같다. 2~3년쯤 시간이 지나니 새로운 한자에 대한 스트레스는 조금씩 사라지고 오히려 궁금증으로 바뀌기는 했는데, 처음에 노래 가사 위주로 외워 공부하다 성조를 안 외워서 지금까지도 고생하고 있기도 하고, 어순도 계속 감이 안 잡혀 헷갈리고 비슷하게 또 8년의 시간이 흘러온듯 하다(강사분은 저한테 창피하다고 어디가면 2~3년만 공부했다고 하라고 한다)

그러다 3년전 정도 쯤에 취미긴 하지만 뭔가 공식적인 기록이 없음 공부했던 시간에 대한 설명을 하기 힘들다고 생각하게 되어, 당시엔 나름 제일 높은 급수였던 JLPT N1 과 HSK 6급(요건 지금 7~9급이 결정되는 시험이 하나 새로 생겨서 조금 상황이 달라졌다)을 목표로 시험을 보기 시작했다. 일본어는 보러갔다 바로 떨어지고, 어휘가 너무 부족함을 느껴서 N2, N1 시험서를 사서 취미 공부의 부작용인 이가 빠진 단어들을 보충했고 다행히 다음 시험에서 턱걸이로 붙게 되었다.

중국어의 경우는 4, 5급은 강사분의 뛰어난 가르침 때문인지 공부를 열심히 한 후 나름 한 번에 붙었지만, 6급에서 계속 허들을 못 넘는 부분이 있어서, 최근에는 너무 언어에 대한 접촉 량이 부족해 그런가 해서 일본어 때를 거울 삼아 취미인 게임도 중국어 언어로 바꿔 해보고, 드라마도 중국어 자막으로 봐보고, 주말에 나름 시험 공부도 틈틈히 해서, 이번 5월에 거의 2년만에 운좋게 시험에 합격 하게 되었다.

무엇보다 이제 딱딱한 시험공부는 안 하고, 이것저것 게임이나 노래, 드라마를 통해서 자유롭게 공부를 할수 있게 된 게 제일 기뻤고, 예전부터 두 개의 언어를 비교해 글이나 영상을 올리고 싶었는데, 생각했던 최소한의 자격을 갖추게 된것 같아서 기뻤다(이젠 누가 뭐라 해도, 이 정도면 N1 이랑 6급은 붙을 수 있을 정도는 된다고 변명하면 되니까...). 두 개의 자격증을 딴 개인적인 느낌은 이제 혼자 공부를 할 수 있을 출발점에 설 수 있게 되었다는 안도감과 동시에 앞으로 갈 길에 대한 막막함도 있다.

여행 다니기를 크게 좋아하지 않는 성격상 중국과 일본에는 강사분들의 구박(언어를 그렇게 오래 배우러 다니면서 왜 자기 나라에 한번도 안 가 보냐는) 및 배우는 언어에 대한 예의로 상하이와 도쿄에 한번씩만 가봤을 뿐이고, 친한 그 나라 사람도 일본 쪽은 아쉽게도 연락이 끊기고, 현재는 중국어 강사 한 분 밖에 없는 상태에서, 업무나 실 생활에서 언어를 쓰고 있는 사람들에 비해선 한없이 아는게 부족한 것은 잘 알고 있다. 그래도 기술적 배경을 가지고 있는 사람이 두 언어를 취미로 나름 천천히 배워서 (아마도 지금 쓰고 한국어를 포함해) 차이를 설명하는 글은 없는 것 같기 때문에, 두 개의 언어와 오랫동안 같이 헤메온 세월을 자격으로 해서 개인적으로 느낀 언어들의 차이를 비교하는 글을 써보려 한다.

추가로 두 언어의 키보드 사용이라든지, 일/중 노래나 한국/일본/중국 노래방 갔을 때의 차이라든지, 게임, 드라마, 두 개의 언어를 동시에 공부할 때의 장 단점, 그리고 꼭 붙은 후 한번 투덜대 보고 싶었던 중국어 시험 내용에 대한 잡담을 섞어서 열 몇 편 정도를 목표로 진행 해보려 한다. 

posted by 자유로운설탕
2022. 9. 4. 19:54 프로그래밍

지인이 새로 책을 냈다고 그래서, 관리가 안되 방문이 드문 블로그 이긴 하지만 간단히 책 소개를 하려고 한다.

예전에 테스트를 직업으로 가졌던 시절만 하더라도(벌써 10년이 조금 더  넘은듯 하다), 임베디드와 관련된 테스트를 하게 되는 것은 일반적으로 경험하기 힘든 영역이였던 것 같은데, 요즘은 모바일 기반으로 중계되는 여러 현실 비즈니스 및 퀵보드 같은 사물 인터넷 기반의 사업 때문에, 소프트웨어 자체가 아닌 연결된 사물의 특성 및 주어진 환경과 같이 테스트 설계 적으로 고려해야 할 부분들이 더 많이 늘어난 것 같다. 추가로 IT쪽의 핫한 추세였던(요즘은 기업들도 몇년 전처럼 맹목적인것 같진 않지만) AI 및 자율주행 등과 같이 센서와 데이터를 통한 자동적인 규칙의 해석이라는 측면에서, 처음부터 요구사항에 의해 개발된 제품에 기반하여 이루어졌던 테스트 영역에 대해서 변화가 생긴 측면도 있는 것 같다.

 

그 하나하나의 주제에 대해서는 각각의 산업들의 히스토리 및 자체 기술이 있고, 해당 기술적인 측면은 업무적 경험과 공부를 통해 하나하나 쌓아갈 수 밖에 없는 것 같긴하지만, 여러 산업 분야에서 다양한 측면으로 경험했던 저자의 배경을 기반으로 전체적인 테스팅 쪽 산업의 모습을 정리해 주는 것에 이 책의 강점이 있는 것 같다. 이미 QA 팀이 오래 동안 성숙되온 커다란 회사는 나름의 철학 등을 가지고 있겠지만, 아마도 그 연혁이 짧거나 겸업의 개념으로 그 직무를 유지해온 회사들은 여전히 여러가지 오해 속에서 헤메면서 QA와 테스팅 업무를 수행하고 있을 것 같다. 이 책은 그렇게 헤메는 사람들에게 업계 전체의 표준과 객관적인 시각으로 자신의 위치를 바라보게 하는 참고 자료가 될 수 있을 것 같다. 개인적으로 개발이나 보안이나, 테스팅 모두 같은 기반을 가진 관점만 다른 분야이긴 것 같긴 하지만 말이다.

 

책을 읽으면서 프로세스와 기법적으로 해석 되어지는 테스팅 전반의 사전적 지식들을 기존의 실무적 경험과 당위성을 기반으로 쉽게 엮어 설명하려고 하는 저자의 노력을 엿볼 수 있었으며, 프로젝트 관리 경험과 컨설팅 경험이 풍부한 저자의 관점에서 테스팅 산업 전반에 대한 숲을 관찰할 수 있게 해준다. 테스팅에 대한 의미와 경향, 버그 및 품질에 대한 이해, 테스팅 계획 및 프로세스, 케이스 작성, 수행, 결과보고, 결함의 원인에 대한 고찰, 팀에 대한 관리, 여러 테스팅 업무 포지션에 대한 현실적으로 주어진 환경 및 장 단점을 이 책을 통해서 경험해 볼수 있다.

 

다만 한가지 실제 개발 코드 예제에 기반하여, 세부적인 다양한 테스팅 설계에 대한 예제 및 자동화 예제가 있다면 좀 더 좋았겠다는 생각이 들지만, 해당 부분은 현재 책과는 다른 방향의 영역이며, 현재도 국, 내외에서 그런 종류의 지식을 디테일하게 설명하는 테스팅 관련 책을 찾아보기는 어렵다 생각하므로, 저자의 다음 책의 서브 주제로 확장되어 발간되기를 기대하는 바램을 가지며 소개를 마쳐본다. 

posted by 자유로운설탕
2021. 9. 15. 21:35 보안

블로그 내용을 기반으로 책이 나오게 되었습니다.

http://www.yes24.com/Product/Goods/103385806

 

구글과 파이썬으로 시작하는 보안 - YES24

보안 분야도 여러 사람이 다양한 생각 및 관점을 가지고 접근하고 이해하겠지만, 이 책에서는 데이터를 따라가는 직업이라는 시작점에서 진행하고자 한다. 챕터마다 보안을 이해하는 데 있어

www.yes24.com

 

전체적인 흐름을 유지하면서 다음과 같은 사항들이 업데이트 되었습니다.

  • 전체 예제를 파이썬 코드로만 진행하도록 변경 하였으며, 내용에 맞추어 예제 코드를 개선 및 추가 하였습니다.
  • 컨텐츠 전체가 바뀌거나 추가된 챕터들이 있으며, 글의 전개나 표현을 가독성 있게 다듬었습니다.
  • 설명을 돕는 120장의 예쁜 삽화들을 지인분이 그려주셨습니다~

posted by 자유로운설탕
2020. 5. 10. 10:30 보안

  이번 시간에는 잘 아는 분야는 아니라고 생각하지만 악성코드라는 분야를 어떻게 바라보면 될지에 대해서 나름의 관점에서 얘기해 보려 한다.

 

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

 

1. 들어가면서

  앞의 글에서도 한 얘기지만 소프트웨어 보안에 마술 같은 부분은 없다. 모든 건 컴퓨터와 연결된 환경내에서 일어나는 일이며, 결국은 코드에 기반하여 논리적으로 설명이 가능해야 방어나 절충이 가능하게 된다. 그건 악성코드와 같은 분야에서도 마찬가지로 적용된다. 악성코드는 비 기술 적이거나 보안을 잘 모르는 사람들에게는 이해할 수 없는 어려운 현상일 수 있다. 악성코드가 컴퓨터에 설치되는 과정에 대해서는 하나하나 취약점의 기술적 측면을 살펴보며 이해해야 하는 문제(물론 이걸로 밥 먹고 사는 사람들이 있는 어려운 분야이긴 하지만)겠지만, 일단 설치된 후에 하는 행동은 일반 프로그램의 경우와 크게 다른 부분이 없다.

 

  예로서 요즘 많은 화제가 되고 있는 우리에게 익숙한 랜섬웨어의 행동을 살펴보자. 걸리면 특별한 경우가 아님 무조건 포맷을 해야 해결이 가능한 악명 높은 이런 악성코드도 사실 동작은 아주 단순하다고 볼 수 있다. 한번 랜섬웨어의 행동을 사람이 따라해 본다고 해보자.

 

 

  누군가 골탕을 먹이고 싶은 사람이 컴퓨터를 로그인 해 놓은채로 자리를 비웠다 하자. 해당 컴퓨터로 재빨리 다가가서 그 사람이 소중하게 생각할 만한 문서 파일이나, 데이터 파일들을 찾아 선택하여 zip으로 압축을 해본다. 확장자도 zip 보다는 다른 누군가가 악의로 했다는 것을 명확히 알아챌 수 있게 “lupine” 이라고 만들어 보자. 내가 아니면 압축을 풀 수 없도록 압축 파일에 나만 아는 복잡한 암호를 걸어서 압축한다(Brute force 공격에 의해서 쉽게 뚫어지지 않도록). 내가 암호를 알려주지 않는다면 아마 영원히 해당 파일의 원본을 찾을 순 없을 것이다. 이후 압축이 안된 원본 파일을 삭제하는데 가능한 복원이 안 되도록 단순히 지우지(delete) 말고, wiping 을 해서 원복 하기 어렵게 만든다. 주의할 점은 시스템에서 사용하는 중요한 파일들은 압축하거나 삭제하면 안된다. 운영체제 동작 자체가 망가져 켜지지 않는다면 상태가 골탕을 먹었는지도 모르게 되니까 말이다. 메모장을 열어 패스워드가 적힌 종이가 적힌 장소를 알려주는 퀴즈를 적은 후, 압축된 파일과 같은 폴더에 복구방법.txt” 라는 이름으로 저장해 놓는다. 뭐 운이 좋음 암호를 찾을 수도 있겠지

 

  위에 사람이 했던 것과 같은 행동을 랜섬웨어는 기존의 악성코드들과 동일한 방법으로 컴퓨터에 설치된 후, 프로그램 코드를 통해서 자동으로 수행한다. 그리고 최종 목표는 상태의 멸망(모든 것을 새로 설치하고 데이터를 날려버리게 하는)이나, 패스워드를 인질로 하고 비트 코인 등을 송금하도록 요구를 한다.

 

 

 

 

2. 백신 프로그램의 입장에서 상상해 보기

  그럼 반대로 이러한 악성코드를 문제가 일어나기 전에 적절히 찾아야 하는 백신 프로그램의 입장은 어떨까? 총기가 허용된 사회에서 테러를 저지를 수 있는 위험한 사람을 찾아내야 하는 것 같은 모호한 입장에 있다고 생각한다.

  왜 모호할까? 겉에서 보이는 행동이외에 사람의 마음속이나 행동의 의도를 알아내긴 힘들기 때문이다. 우선 총을 가지고 있다고 위험한 사람일까? 물론 가능성은 높을 것이다. 하지만 테러를 일으킬 수 있는 사람일 수도 있지만, 단순히 사복을 입은 경찰관일 수도 있고, 사회 분위기가 어수선해서 자신을 보호하기 위해 총기를 휴대한 사람일 수도 있다. 그럼 총기 허가증이 있거나, 경찰이라면 안심할 수 있을까? 반대로 테러를 위해서 치밀하게 준비된 시나리오일 수도 있다. 그럼 어떻게 해야 할까? 1시간 정도만 그 사람의 행동을 관찰하면 될까? 아니 어쩌면 그 사람의 작전 D-day10일 뒤이기 때문에 하루 종일 살펴봐도 이상한 징후는 없을지도 모른다. 총을 주머니 바깥으로 빼내서 겨눈다고 나쁜 사람일까? 아니 무언가 수상한 범죄자를 보고 총을 겨누고 있는 형사일수도 있다.

 

  마이너리티 리포트 영화 같은 대상의 악의성을 판단하는 이런 판단의 문제는 백신 업체를 무척 머리 아프게 만드는 측면일 것이다. 그래서 백신이 멀쩡히 깔려 있는 컴퓨터에서 새로운 랜섬웨어가 걸리는 것 같은 이해할 수 없는 일들도 일어 날테고 말이다. 위의 테러범을 찾는 문제로 간다면, 검출이 안되는 플라스틱 총이나, 케익 상자로 위장 할 수도 있고, 경찰이 테러범과 비슷한 형태로 행동할 수도 있는 상식적인 선을 넘는 여러 시도가 일어날 수 있기 때문이다. 심지어는 테러범을 수색하는 사람으로 위장한 사람이 나타날 수도 있다. 영화나 부패한 나라에서 일어나는 일이지만 아래와 같이 현실 상에서도 소프트웨어 세계에 비슷한 일이 발생할 수가 있다.

 

[PC PC 검색어 조작 해킹 심은 일당 검거 - 아이러브피씨방]

http://www.ilovepcbang.com/news/articleView.html?idxno=73794

 

[사설 보안 연구소장이 해킹.. 국내 PC방 절반 감염 - YTN]

https://www.ytn.co.kr/_ln/0115_201611142210438443

 

[허위 백신 - 나무위키]

https://namu.wiki/w/%ED%97%88%EC%9C%84%EB%B0%B1%EC%8B%A0

 

 

 

 

3. 백신 프로그램이 할 수 있는 전략을 상상해 보기

  최신의 백신 프로그램이 어떤 무기와 전략을 가지고 돌아가는 지는 해당 회사에 있지 않은 이상은 모를테지만, 책이나 기사에서 본 것을 토대로 상식적으로 접근해 볼 순 있을 것이다. 먼저 정적 분석이라고 하는 분야가 있다. 상식적으로 이해하기 위해 위의 테러범을 찾는 문제의 과점에서 생각하면 이해가 좀더 쉬울 것이다.

 

  먼저 의심이 가는 사람의 외모적 특징을 본다. 그 담에 지문이나 신분증을 확인하며 이상한 이력이 없는지 전산을 조회해 본다. 이후 소지품 검사를 해서 총이나 수상한 물품을 소지했는지 체크하고, 총기 허가증을 체크한다. 상황에 따라 일반적으로 차별이 금지되어 있지만 테러 의심자를 조사하는 특수한 경우에만 허용된 여러 편향적인 부분 또한 체크할 수도 있을 것 같다(국적, 종교, 지인, 직업 등).

 

  그 다음은 동적 분석인데, 해당 부분으로 통과된 사람이라도 뭔가 의도적으로 정상적으로 위장한 사람일 수도 있기 때문에 여러 동적인 행동도 체크해 본다. 위험하다고 분류된 특정한 행동을 한다든지, 불안한 패턴을 보인다든지, 특정한 사람과 연락이나 대화를 한다든지 하는 부분 말이다. 데이터 분석 및 머신러닝 등을 이용해 해당 패턴들을 과거의 테러범들의 데이터에 비교하거나 특이한 부분을 찾아 수상한 패턴을 찾을 수도 있을 것이다.

 

  비슷하게 백신 프로그램도 실행이 가능한 파일들에 대해서 같은 조사를 할 수 있다. 파일내의 특정 이미지나 텍스트를 검사하거나(외모), 파일의 해시 값(지문, 주민번호)을 알려진 악성코드의 해시 값과 비교하거나, 특정 바이트의 특징(신체적, 사회적 특징)을 찾거나, 프로그램 코드를 따라가며 위험한 행동을 하는 코드를 찾거나 할 수 있다. 조금 더 나아가 사용하는 라이브러리나 패커 등의 패턴 등을 기존에 구축된 악성코드 데이터베이스를 기반으로 비교해 수도 있을 것이다.

 

  나아가 샌드박스 등으로 제한된 환경에서 프로그램을 실행 시켜 이상한 행동을 하는지 지켜보거나, 수상한 외부 사이트와 연결해 데이터를 주고 받거나 하는지도 체크할 수 있을 것이다.

 

 

 

 

4. 프로그램 행동 분석의 명암

  이상적으로는 위의 액션들을 통해 위험한 것을 전부 발견할 수 있겠다고 하고 싶겠지만, 여기에는 몇 가지 제약들이 얹어지게 된다. 분석을 방해하는 암호화된 팩커들이 존재하고(이건 뭐 실행 시점엔 원래대로 돌아오니 본질적으론 괜찮다고 싶다하고), 분석시간에 제한도 생긴다(백신 파일이 내가 실행하려는 파일을 한없이 잡고 있는 걸 바라는 사용자는 없다), 감시 당한 다는 것을 알고 감시 안 당할 때 행동을 시작하려는 악성코드도 있을 수 있고, 특정 조건 하에서만 특정한 코드를 실행하는 경우도 많을 것이다. 앞의 피씨방 프로그램처럼 대부분의 기능은 정상적인 프로그램이고 그 안에 숨어 못된 행동을 할 수도 있다.

 

  결국 위의 정적인 분석, (세미) 동적인 분석, 디버거, 디스어셈블리 툴 같은 것을 이용한 파일 분석 등이 필요할 텐데 디버거나 디스어셈블리 툴을 통한 분석은 가장 해당 프로그램의 진실에 가깝게 접근하겠지만 아무리 자동화로 구축을 하더라도 분석 하는 사람의 경험&재능 및 어느 정도의 인력, 시간 등의 제약요소를 가진 노동 집약적인 특징을 피할 수는 없을 것 같다. 머신러닝 등의 데이터를 기반한 분석을 하더라도, 수많은 악성코드 데이터를 모두 모으고 신규 생성되는 코드 또한 실시간으로 수집해 데이터베이스를 재 구축하는 부분도 역시 쉽지 않은 도전이 될 것 같고, 그렇게 하더라도 앞으로 일어날 새로운 패턴의 공격을 막을 수 있을지는 확실히 자신하긴 힘들 것 같고 말이다. 그래서 아무래도 방어하는 쪽 보다는 공격하는 쪽이 맘이 많이 편하긴 할 듯 싶다.

 

 

 

 

5. 동적 분석의 예 및 회피 시도 해보기

  파일내의 문자열이 패턴 분석 같은 정적 분석에 대한 예제 보다는, 동적 분석의 예를 보이면 좋을 것 같아서 처음엔 Cuckoo Sandbox 같은 오픈소스 샌드박스를 구축해 파일을 실행해 볼까 했지만, 찾아보니 원하는 결과를 얻기까지 가야 될 길이 꽤 복잡해 보이고, Virus Total 사이트를 보다 보니 기본적인 동적 분석을 제공 해주는 것 같아서 해당 사이트를 이용해 가볍게 시연을 해보려고 한다.

 

  시연에 사용할 악성 파일 샘플의 경우도 굳이 백신을 잠시 꺼야 할지도 모르는 실제 덜 위험한 악성코드나 자바나 C언어 등을 이용한 실행 파일을 만들기는 번거로울 듯 해서, 12교시 리버싱과 포렌식 편에서 만들었던 엑셀의 최근 열린 파일을 알아내기 위해 레지스트리 키를 읽어왔던 아래의 파이썬 샘플을 파이썬 인터프리터가 포함된 하나의 exe 파일로 만들어 해당 파일을 이용하도록 해본다 (실행 파일이 레지스트리를 읽는 동작도 동적 동작에 포함되기 때문에 샌드박스에서 찾아낼 것이다).

1
2
3
4
5
6
7
8
9
10
11
import winreg
# 키를 정의 한다.
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\16.0\\Excel\\File MRU")
 
# 키 안에 담김 value 숫자를 얻어 그 숫자 만큼 루프를 돈다.
for i in range(0, winreg.QueryInfoKey(hKey)[1]):
    name, value, type = winreg.EnumValue(hKey, i)
 
    # Item 이라는 문자열로 시작하는 이름일 때 프린트 한다.
    if name.startswith("Item"):
        print (name + ": " + value)
cs

[show_excel_recently.py]

 

  현재 진행을 하고 있는 버전이 python 3.6.4 버전이라는 것을 잊지 말고(다른 버전에서는 아래 방식이 지원이 안되면 다른 방법으로 exe 를 만들어야 할 수도 있다), 우선 pyinstaller 를 설치해 보자.

c:\Python\code\>pip install pyinstaller

 

  이후 13교시에 만든 show_excel_recently.py 파일을 아래 명령어를 통해서 하나의 exe 파일로 만들어 보자

c:\Python\code>pyinstaller --onefile -w show_excel_recently.py

 

  해당 명령어가 잘 돌아가게 되면, "c:\python\code\dist" 폴더에 “show_excel_recently.exe” 파일이 생성되어 있을 것이다(해당 파일을 실행한다고 커맨드 창에 결과를 뿌리진 않는다. 아마 그렇게 보여주려면 파이썬 파일 수정이 좀 필요할 듯 보이는데 금번 시연 과정에서 굳이 그럴 필요는 없는 것 같아서 그냥 안 되는채로 두려고 한다)

 

 

  이제 바이러스 토탈 사이트로 이동해 보자. “Choose File” 버튼을 클릭해 방금 전에 만든 exe 파일을 업로드해 보자. 분석이 시작되고 조금 후에 결과 화면이 나온다.

https://www.virustotal.com/gui/home

 

  기본적인 정적 분석 결과가 나오고, 판단 이유는 모르겠지만, 69개 중 4개의 바이러스 엔진이 이 파일이 좀 위험한 거 같다고 얘기했다고 한다. “BEHAVIOR” 탭을 클릭해 본다(처음 올렸다면 분석 후 결과가 표시되는데 조금 시간이 걸릴 수도 있다)

 

  그럼 해당 exe 프로그램이 생성 수정한 파일들 이라든지, 레지스트리 키, 프로세스, 사용한 DLL, 특이한 액션 등 여러 정보 들이 표시되게 된다. 이 파일이 뭔가 특별한 일은 하는게 아니고 레지스트리만 읽어 왔기 때문에, “Registry Actions” 파트 쪽을 보자. 그럼 두 번째 줄에 우리가 코드를 통해 접근한 엑셀 키가(HKCU\…\Excel\File MRU) 보이게 된다.

 

 

 그럼 앞의 테러범 얘기로 돌아가서 한번 더 역으로 악성코드 입장에서 생각해 보자. 내가 동적 검사를 당했을 때, 거기에 검문당하지 않고 순진한 파일로 인식되려면 어떻게 해야 할까? 가장 좋은 방법은 지금 감시당하고 있다는 것을 인지하고 발톱을 숨기는 것일 테고, 아주 단순한 방법은 적당히 단속이 끝날 시간까지 몸조심하면서 아무 일도 하지 않는 것일 것이다.

 

  파일을 올린 후를 생각해 보면 우리가 동적 분석 결과를 얻기까지 그렇게 오랜 시간이 걸리지 않았고, 그렇게 오랫동안 관찰하는 방식은 검사하는 프로그램의 ROI 나 파일이 실행되기를 기다리는 사용자의 불편을 초래하기 때문에 힘들듯 싶으니, 우리가 원하는 레리스트리 키를 읽는 동작을 하기 전에 일정 시간 쉬었다 읽게 됨 동적 검사를 빠져나갈 수 있지 않을까 싶다.

 

  그럼 그 가설을 한번 체크해 보기 위해서 파이썬 코드를 조금 수정하여, 1분 동안 잠시 쉰 후(sleep), 동작을 하게 하도록 만들어 보자. show_excel_recently_sleep_1m.py 이라는 새 이름으로 저장해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import winreg
import time
 
# 10초를 쉰다.
time.sleep(60
 
# 키를 정의 한다.
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\16.0\\Excel\\File MRU")
 
# 키 안에 담김 value 숫자를 얻어 그 숫자 만큼 루프를 돈다.
for i in range(0, winreg.QueryInfoKey(hKey)[1]):
    name, value, type = winreg.EnumValue(hKey, i)
 
    # Item 이라는 문자열로 시작하는 이름일 때 프린트 한다.
    if name.startswith("Item"):
        print (name + ": " + value)
cs

[show_excel_recently_sleep_1m.py]

 

  이후 다시 exe 파일을 만들어 본다. show_excel_recently_sleep_1m.exe 라는 이전 파일과 하는 동작은 같지만 1분 정도 멈췄다 레지스트리 키를 읽는 파일을 만들어 본다.

c:\Python\code>pyinstaller --onefile -w show_excel_recently_sleep_1m.py

 

  같은 방식으로 바이러스 토탈 사이트에 해당 exe 파일을 업로드 후 결과를 보면, 우리가 기대했던 바와 같이 레지스트리의 엑셀 정보를 접근한다는 정보는 보이지 않는다. 이렇듯 검사하는 쪽은 시간에 쫒길테지만 공격하는 쪽은 1분후에 공격한다고 뭐 특별히 문제가 있지 않는 상황이 된다. 그래서 공격하는 쪽이 압도적으로 회피를 하기엔 유리하다고 본다.

 

 

 

6. 마무리 하면서

  생각하면 할수록 악성코드에 대한 전쟁은 방어하는 쪽이 압도적으로 불리한 영역 같다는 생각이 든다. 그래서 자동화 파트에서도 비슷한 얘기를 했었지만 이제는 작은 개인은 백신과 같은 지식 집약적 영역에서 빛을 발하기는 힘들다. 백신 프레임워크를 개발하는 것에 대한 실력의 문제가 아니라 앞서 얘기한 방대한 데이터들과, 악성 행동들에 대한 정의의 모호함, 순진한 프로그램과 유사하게 보이는 사회공학적, 현실적 측면(점유율 높은 PC방 프로그램 회사를 인수해 악성코드를 심으리라고 누가 쉽게 생각하겠는가?)을 추가해 점점 정교해지는 공격 패턴 등에 대한 집단 지성에 가까운 다양성 및 양적 범위를 커버하기 힘들 것 같기 때문이다. 어찌 보면 기술과 돈과 전략을 겸비한 거대한 단체들의 물량과 물량의 싸움으로도 보인다.

 

  악성코드에 대한 안전함은 현실적으로는 OS 개발 사의 취약점 패치와, 잘 알려진 공격 패턴들에 대해 빠르고 정확하게 방어하는 백신, OS 단에서 악성코드와 정상코드의 행동 구분을 원활하게 만드는 보안 설계 등에 의존할 수밖에 없는 것 같다. 그래서 OS 나 프로그램 패치를 열심히 하고, 백신 패턴을 최신으로 유지하며, 불법적인 무료 이익을 주는 프로그램이나 사이트를 이용하지 말고, 랜섬웨어 같은 단순하지만 강력한 무기들을 대비해 백업을 안전하게 해놓는 것이 개인으로선 최선인 것 같다(여러가지 예외 상황 때문에 백신만으로는 100% 못 막을 것이라는 것을 가정하는 게 더 현명한 방어 전략일 것 같다). 방어를 하는 입장에서의 여러 기술들의 최적화와 자동화에 대해서는 우리가 모르는 각 보안 회사들만의 여러 노하우들이 많이 있을 것 같고 말이다.

 

  소극적인 편법의 측면으로는 공격자가 가치를 모르게 데이터를 잘 위장해 놓는 것도 하나의 방법이 될 것 같기도 하다. 공격자 입장에서는 은폐와, ROI를 위해 최소한의 타겟팅 공격을 하는 편이고, 한편으로는 어차피 속고 속이는 싸움 같아 보이기 때문이기 말이다. 방어자 또한 꼭 정직하게 행동해야 할 필요는 없어 보인다. 뭐 하지만 위장 방법의 깊이가 깊지 않다면, 또는 위장이라는 것은 zip 파일의 복잡한 패스워드처럼 (현실적으로) 완벽한 노출에 대한 대응 수단은 아니기 때문에, 수법이 드러나는 순간 당한지도 모르고 당하게 되는 리스크가 있기 때문에 기본적인 정책에 추가해 상대를 어렵게 만드는 보조적인 수단으로만 생각해야 할 듯도 싶다. 원숭이도 나무에서 떨어지는 법 이니까 말이다. 자기확신만큼 무서운 건 없다.

 

 

  또한 악성 코드에 대한 자동이나 수동 판단에 대한 모호성 문제는 사실 보안의 많은 분야에서 마찬가지로 발생한다. 그래서 이상적인 관점만큼 100% 안전한 것은 보안 분야에 걸쳐 사실상 드문거 같다. 그래서 이쪽 분야에 대해 많이 알게 될수록 그 한계 또한 알게 되며, 겸손하게 되는게 일반적인 성장 패턴일것 같다.

 

 

 

2020.5.10 by 자유로운설탕
cs

 

 

 

 

 

 

posted by 자유로운설탕
2020. 5. 4. 19:29 Japan Pop

  중국어를 공부하다 일본어 학원을 다시 잠시 다녔을 때 만난 고등학생 애한테 요즘 듣는 가수들 추천을 해달랬더니, 정리해준 가수들이 모두 락 쪽이였다. 락은 나한테 조금 힘든데 하는 맘은 있었지만, 정리해 준 것에 대한 고마움과 예의 때문에 유투브를 통해 하나하나 가수를 찾아 들어봤다(요즘은 사람들이 좋아하는 취향들에 대해서는 내가 이해를 못하더라도 나름 시간을 들여 이해할수 있다면 그들이 좋아하게 되는 특별한 매력이 있겠지 하는 생각이 든다). 그 중 조금 맘에 드는 노래가 있어서 마침 코인 노래방에도 있고 해서 도전. 막상 불러보니 들을 때 잔잔한 느낌과는 다르게, 후렴에서 뭔가 가볍게 지르는 느낌에 부르는 재미도 있었다. 그래서 곡이 영화의 OST 인것 같아 좀 더 노래의 느낌을 이해하고 싶어서 영화를 찾아 보게 됬다.

 

ぼくは明日、昨日のきみとデートする(나는 내일 어제의 너와 만난다)

https://namu.wiki/w/%EB%82%98%EB%8A%94%20%EB%82%B4%EC%9D%BC%2C%20%EC%96%B4%EC%A0%9C%EC%9D%98%20%EB%84%88%EC%99%80%20%EB%A7%8C%EB%82%9C%EB%8B%A4(%EC%98%81%ED%99%94)

 

  영화는 라이트 노벨을 원작으로 한 거라 해서 그닥 거슬리진 않는 약간의 판타지가 섞여 있다. 다만 조금 일본 드라마, 영화 특유의 미약한 사람의 손이 닿지 않는 커다란 운명의 열려있는 결말로 끝나서, 보고나면 마음 한쪽이 시큰거리는 허무함이 있다. 아마 왠지 이 노래는 영화를 보고 만든건 아니였을 것도 같은데, 가사랑 영화의 내용이 은근 잘 매치가 된다. 다만 영화쪽이 좀 더 깊고 섬세한 감정이라 노래가 좋다면 영화도 봐보는 것을 추천 한다. 아는 지인은 반대로 영화를 보고 엔딩 부분에 나오는 이 노래가 좋아 찾아봤다는 경우도 있다. 코인 노래방을 좋아하는 사람이라면 코로나 끝나면 꼭 가서 도전해 보기를(요즘 지역 경제엔 미안하지만 주변의 안전을 위해 코인 노래방을 몇달째 못 가서 금단 현상이 가득하다)...

 

  중국 노래는 성조 때문에 노래로 언어를 배우는 것에 찬반이 있는 것 같다. 하지만 개인적으로는 아무거라도 해당 언어의 교과서 공부 틀을 벗어나는 경험을 하는 것은 나쁘진 않은거 같다. 노래 안에서도 성조가 들린다는 것은 아직 이해가 잘 안가지만 말이다. 경험상 노래엔 필요없어 보여서 성조를 같이 안외고 발음만 외어버리면 나중에 엉터리로 각인되버린 성조들을 다시 교정하느라 돌 엄청 맞게되니 모르는 한자가 많이 나오고 노래속에선 성조를 표현을 못하더라도 성조를 같이 외자. 다른 한편으로는 회화는 형편없는 편이라 해당 가설이 맞나 싶기도 하다.

 

  하지만 일본어의 경우는 노래를 보다보면 동사의 변형, 원형 등도 반복해 유추하게 되고 교과서엔 안 나오는 단어나 어휘도 많이 보게되서 장점이 더 많은 듯 싶다. 또 빠른 노래를 연습 하다보면 한자 읽기에 대한 순발력이 길러지지 않나도 싶다. 노래할 때 도움을 주는 히라가나로 된 한자 발음이 오히려 귀찮게 눈에 자꾸 거슬리게 된다면 나름 성공이다. 중국 노래와 마찬가지로 나름 해석 해보고 애매하거나 자신 없는 부분은 지인한테 물어서 보충했다. 다른 일도 마찬가지 겠지만 언어를 공부할 때 편하게 물어볼 수 있고, 귀찮아 하지 않고 알려주고 싶어하는 사람이 있는 건 정말 좋은일 같다.

 

 

 

さよならが喉の つっかえてしまって

안녕이라는 말이 목 안에 걸려 버리고,

咳をするみたいに ありがとうって言ったの

기침을 하는 것처럼 고맙다고 말했어

次の言葉はどこかと ポケットを探しても

다음엔 어떤 말을 할까 하며 주머니를 뒤져봐도

見つかるのはあなたを好きな私だけ

찾을 수 있는 건 너를 좋아하는 나 자신 밖에

 

よ大丈夫だよ

아무렇지 않아, 괜찮아

優しくなれたと思って 願いにわって最後はになって

상냥하게 되었다고 생각하고, 소원으로 변해, 마지막엔 거짓말이 되어

(뭔가 이별을 예고되 맘이 아프지만 상대방이 알아채지 못하게 친절하고, 다정하게 대하려고 노력했지만 결국엔 모든 게 현실의 무게에 치여 거짓말이 되어버렸다 정도로 이해하면 어떨까 싶다)

いまま枯れてゆく あなたを好きなままで消えてゆく
푸르른 채로 시들어 가, 당신을 좋아하는 채로 사라져 가

(이게 정말 푸르다는 건지 아직 다 익지 못한 설익은 단계라는 건지는 알송달송)

私みたいと手に取って

나 처럼 손에 쥐고

にあった想いと一に握り潰したの

안에 있던 마음과 함께 쥐어 부셔 버렸어
(위의 두 줄은 어떤 이미지를 표현하는 가사인지 잘 모르겠음)

大丈夫 大丈夫

괜찮아 괜찮아

今すぐに抱きしめて

지금 바로 껴안고 싶은데

私がいれば何もいらないと それだけ言ってキスをして

내가 있으면 아무 것도 필요 없다고 그렇게 말하며 키스를 해줘

なんてね だよ ごめんね

농담이야 거짓말이거든 미안해

 

こんな時思い出す事じゃ ないとは思うんだけど

이런 때에 생각이 날 일은 아니라고 생각하지만

一人にしないよって あれは嬉しかったよ

혼자 있지 않아도 되서 그거 사실은 기뻤어

(しないよって 해석이 애매함)

あなたが勇を出して 初めて電話をくれた

당신이 용기를 내서 처음으로 전화해 줬던

あの夜の私と何が違うんだろう

그 밤의 나와는 무엇이 다른 것  일까?

どれだけ離れていても  どんなにえなくても

얼마나 멀리 떨어져 있더라도, 아무리 못 만나게 되더라도

持ちがわらないから ここにいるのに

마음이 변하지 않기 때문에 여기에 있는 거니까

いまま枯れてゆく あなたを好きなままで消えてゆく

푸르른 채로 시들어 가, 당신을 좋아하는 채로 사라져 가

私をずっとえていて

나를 계속 기억해 줘

なんてね だよでいてね ああ

농담이야 거짓말이거든 건강하게 잘 지내줘

ララララ ララララ

ララララ ララララ

 

泣かない私に少し ほっとした顔のあなた

울지 않는 나에게 조금은 안심한 얼굴의 너

わらず暢 そこも大好きよ

여전히 느긋한 거 같아 그 것도 너무 좋아

が付けばにいて 別に君のままでいいのになんて

맘에 걸린 다면 옆에 있어줘, 그냥 당신 그대로 괜찮으니까

勝手に 拭いたくせに

제멋대로 눈물을 훔쳤던 주제에

見える全部こえる全て 色付けたくせに

보이는 것 전부, 들리는 것 전부 색칠해 버린 주제에

(아마 실제와는 다르게 상대방의 맘이 편하게 괴롭지 않은 척 연극했다는 것 같음)

いまま枯れてゆく あなたを好きなままで消えてゆく
푸르른 채로 시들어 가, 당신을 좋아하는 채로 사라져 가

私みたいと手に取って

나 처럼 손에 쥐고

にあった想いと一に握り潰したの

안에 있던 마음과 함께 쥐어 부셔 버렸어

大丈夫 大丈夫

괜찮아 괜찮아

今すぐに抱きしめて

지금 바로 안고 싶은데

私がいれば何もいらないと そう言ってもう離さないで

내가 있으면 아무 것도 필요 없다고 그렇게 말하며 더 이상 떠나지 말아줘

なんてね だよ さよなら

농담이야 거짓말이거든 그럼 안녕

 

 

 

 

[서비스] 한자 발음 및 원형

(のど), (おく), (せき), 言う(いう)

(つぎ), 言葉(ことば), 探す(さがす), 見つかる(みつかる), 好き(すき), (わたし)

 

(へいき), 大丈夫(だいじょうぶ)

優しい(やさしい), 思う(おもう), 願い(ねがい), わる(かわる), 最後(さいご), (うそ)

(あおい), 枯れる(かれる), 消える(きえる), (), 取る(とる)

想い(おもい), (いっしょ), 握り潰す(にぎりつぶす)

 

(いま), 抱きしめる(だきしめる), (なに), (うそ)

 

(とき), 思い出す(おもいだす), (こと), 一人(ひとり), (じつ), 嬉しい(うれしい)

(ゆうき), 初め(はじめ), 電話(でんわ), (よる), 違う(ちがう)

 

離れる(はなれる). (あう), (きもち), える(おぼえる), (げんき)

 

泣く(なく), 少し(すこし), (かお), わらず(あいかわらず), (のんき), 大好き(だいすき)

(よこ), 別に(別に), (きみ), 勝手(かって), (なみだ) 拭く(ふく)

全部(ぜんぶ), (きく), 全て(すべて), 色付ける(いろづける), 離す(はなす)

 

posted by 자유로운설탕
2020. 4. 18. 23:34 China Pop

  역사적인 관계가 얽혀있는 다른 나라의 언어를 배운다는 것은 종종 묘한 부분이 있다. 특히 두 나라 사이의 감정이 격해 질때면, 관련된 언어를 공부하고, 해당 언어로 만들어진 여러 문화적인 부분을 경험하고 노출하는 것 조차 괜히 눈치가 보일때가 있다(개인적으로는 코인 노래방에서 중국-일본 노래 부르기, 지하철에 앉아 해당 언어 펼치고 공부하기 등). 뭐 살아가면서 역사적 관계나 이익의 충돌이 전혀 없는 나라의 언어를 배우게 될 일이 있을지는 모르겠지만, 하나의 언어를 배우면서 그 나라 사람들을 만나게 되면 추상적으로 생각해 오던 해당 나라가 구체적인 모습으로 다가 오게 되는 측면도 있는 것 같다.

 

  이 노래는 강사분이 추천해 줬던 노래로 이미 여러 블로그에서 소개되어 있지만, 처음으로 스스로 번역해서(이상한 곳 체크를 받고) 올렸다는 데에 의미가 있을 듯 싶다^^. 워낙 노랫말처럼 가랑비에 옷 젖듯 천천히 공부해온 탓에 그렇긴 하지만, 노래 가사는 시와 비슷하게 생략된 문장 형태인 경우가 많고, 중국어 특유의 함축적이고 중의적인 한자의 느낌 때문에, 원래 쓴 사람의 의도를 잘 파악하기 어려운 때가 많은 듯 싶다. 뭐 그것은 이 글의 해석의 어딘가가 매끄럽지 않은데에 대한 변명이기도 하다--;

 

  개인적으로 중국은 고전적인 가치관이 여전히 사회의 많은 부분을 감싸고 있는 것으로 보인다. 현재의 시대를 살아감에 있어 고전적이라는 것은 구닥다리 느낌의 부정적인 측면도 있지만, 나아가는 것이 꼭 좋아지는 것만은 아니라는 측면에서는 긍정적인 부분도 동시에 존재하는 것같다. 물론 한 개인이 사회에서 둘 사이에 적당한 균형을 이루고 살긴 많이 어려운 것 같지만 말이다. 이 노래는 중국 사회의 고전적인 감정에 담긴 잔잔한 우아함을 한자에 담아 표현하고 있다고 생각한다. 참고로 한국 노래방 기계에는 없는 듯해서 많이 아쉽다^^~

 

 

 

 

书里总爱写到喜出望外的傍晚
Shūlǐ zǒng àixiědào xǐchūwàngwài de bàngwǎn

책속에는 항상 뜻밖의 기쁨을 주는 저녁 무렵의 풍경이 담겨 있죠.

 

骑的单车还有他和她的对谈
de dānchē háiyǒu tā hé tā de duìtán
자전거를 타거나 그와 그녀가 이야기를 나누는 모습

(책 속에 담긴 여러 사랑 이야기 들의 장면을 떠올리는 듯...)

 

女孩的白色衣裳男孩爱看她穿
hái de báishang nánhái àikàn tā chuān

하얀 옷을 입은 여자아이를 바라 보는 것을 좋아하는 남자아


好多桥段    好多都浪漫
hǎoduō qiáoduàn   hǎoduō dōu làngmàn
얼마나 많은 일들이,  얼마나 많은 낭만이

(桥段 은 보통 영화 연극 등의 scene 같은 것을 말하는 듯. 특정한 갈등이 있는 상황이나 계기 등? 적당한 표현할 단어가 생각 안나 함축적으로? "일"이라고 해석. 연인들 끼리의 친해지는 과정이나, 다툼에 대한 에피소드라고 보면 어떨까 싶다)

 

好多人心酸    好聚好散
hǎoduō rén xīnsuān    hǎo jù hǎo sàn
얼마나 많은 사람이 가슴이 아프고, 모였다 흩어졌을까요

 

好多天都看不完
hǎoduō tiān dū kànwán

여러 날이 지나도 다 못볼 만큼 많죠

(위와 같은 많은 감정의 편린들이 있어서 여러 날을 봐도 못 볼만큼 많다는 의미)

 

 

 

 

刚才吻了你一下你也喜欢对吗
gāngcái wěn le nǐ yīxià nǐ yě xǐhuān duì ma
방금 내가 입맞췄을 때 당신도 좋았던 맞죠? 

 

不然怎么一直牵我的手不放
rán zěnme yīzhí qiān wǒ de shǒu bù fàng

그렇지 않다면 내손을 왜 줄곧 잡고 있겠어요.

 

你说你好想带我回去你的家乡
nǐ shuō nǐ hǎoxiǎng dài wǒ huíqù nǐ de jiāxiāng
당신은 나를 고향으로 데리고 가고 싶다 말했죠

 

绿瓦红砖    柳树和青苔

wǎ hóng zhuān    liǔshù hé qīng tái
녹색기와와 붉은 벽돌, 버드나무와 푸른 이끼

 

过去和现在    都一个样
guòqù hé xiànzài    dōu yī gè yàng
과거와 현재, 모두 한결같죠 

 

你说你也会这样
nǐ shuō nǐ yě huì zhè yàng

당신은 당신도 그렇게 변하지 않을거라고 말했죠

 

 

 

 

慢慢喜欢你    慢慢的亲密
mànman xǐhuān nǐ    mànman de qīnmì
천천히 당신을 좋아하고, 천천히 가까워지고

 

慢慢聊自己    慢慢和你走在一起
mànman liáo zìjǐ    mànman hé nǐ zǒu zài yīqǐ
천천히 자신에 대해 얘기하고,  천천히 당신과 걸어가기

 

慢慢我想配合你    慢慢把我给你
mànman wǒ xiǎng pèihé nǐ   mànman bǎ wǒ gěi nǐ

천천히 당신과 어울리고, 천천히 당신에게 나를 주고 싶죠

 

慢慢喜欢你    慢慢的回忆
mànman xǐhuān nǐ    mànman de huíyì
천천히 당신을 좋아하고, 천천히 추억하고

 

慢慢的陪你慢慢的老去
mànman de péi nǐ mànman de lǎoqù

천천히 당신과 함께 있고, 천천히 나이를 먹어가기

 

因为慢慢是个最好的原因

yīnwèi mànman shì gè zuì hǎo de yuányīn

왜냐하면 느리다는 건 아주 좋은 이유 중 하나기 때문이죠

(原因 은 "원인"이라는 뜻이긴 하지만, "느리게" 무언가를 쌓아가는게 무언가 하나의 두 사람 사이의 완전한 사랑을 만들게 된다는 의미로 생각하면 어떨까 싶다. 같은 완전함 이래도 불 같은 사랑과 반대라고 할까? 그래서 사랑이 이루어지게 되는 "이유" 중 하나라고 옮김)  

 

 

 

晚餐后的甜点就点你喜欢的吧
wǎncān hòu de tiándiǎn jiù diǎn nǐ xǐhuān de ba
저녁 식사 후의 디저트는 당신이 좋아하는 걸로 주문해요

 

今晚就换你去床的右边睡吧
jīnwǎn jiù huàn nǐ qùchuáng de yòubiān shuì ba

오늘 밤엔 당신이 오른 쪽에서 잠을 자요

(아마 침대 오른쪽이 벽이고, 평소에는 바깥 쪽에서 남편이 자다가 뒤에 나오 듯 내일은 여행을 가는 일에 설레는 부인이 먼저 일어나서 준비하며 기다리기 위해서 바깥 쪽인 왼쪽에서 자겠다는 의미로 추측) 


这次旅行我还想去上次的沙滩
zhècì lǚxíng wǒ hái xiǎngqù shàngcì de shātān
이번 여행 때 지난번 갔던 그 해변에 다시 가보고 싶어요.

 

球鞋手表    袜子和衬衫都已经烫好    放行李箱
qiúxié shǒubiǎo    wàzi hé chènshān dōu yǐjīng tànghǎo    fàng xínglǐxiāng
운동화와 시계, 이미 다려놓은 양말과 셔츠는, 트렁크에 넣어놨어요.

早上等着你起床
zǎo shang děng zhe nǐ qǐ chuáng

아침에 당신이 일어나기만 기다리고 있어요.

 

 

 

[이 후는 반복]

慢慢喜欢你    慢慢的亲密
mànman xǐhuān nǐ    mànman de qīnmì
천천히 당신을 좋아하고, 천천히 가까워지고

 

慢慢聊自己    慢慢和你走在一起
mànman liáo zìjǐ    mànman hé nǐ zǒu zài yīqǐ
천천히 자신에 대해 얘기하고,  천천히 당신과 걸어가기

 

慢慢我想配合你    慢慢把我给你
mànman wǒ xiǎng pèihé nǐ   mànman bǎ wǒ gěi nǐ

천천히 당신과 어울리고, 천천히 당신에게 나를 주고 싶죠

 

慢慢喜欢你    慢慢的回忆
mànman xǐhuān nǐ    mànman de huíyì
천천히 당신을 좋아하고, 천천히 추억하고

 

慢慢的陪你慢慢的老去
mànman de péi nǐ mànman de lǎoqù

천천히 당신과 함께 있고, 천천히 나이를 먹어가기

 

因为慢慢是个最好的原因
yīnwèi mànman shì gè zuì hǎo de yuányīn

왜냐하면 느리다는 건 아주 좋은 이유 중 하나기 때문이죠

 

 

书里总爱写到喜出望外的傍晚
shū lǐ zǒng ài xiě dào xǐ chū wàng wài de bàng wǎn

책속에는 항상 뜻밖의 기쁨을 주는 저녁 무렵의 풍경이 담겨 있죠.

 

 

posted by 자유로운설탕
2019. 10. 6. 18:05 보안

  이번 시간에는 모니터링 이라는 주제에 대해서 얘기해 보려 한다. 사실 모니터링은 보안 뿐만 아니라 모든 IT 영역에서 관심을 가지는 부분이기도 하지만, 간단한 예제와 함께 보안에서의 모니터링이란 무엇일까에 대해서 가볍게 생각해 보려 한다.



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터


 


 


1. 들어가면서

  흔히 물리적 보안을 소프트웨어 적인 보안 분야보다 명확하고 쉬운 분야로 취급하는 경향이 많은 것 같긴하지만, 두 개의 분야는 무척 유사하고 구분 하긴 힘든 연결 고리를 가지고 있다고 생각한다. 예로서 특정한 가게를 도난에 안전하게 지키려고 노력한다고 생각해 보자. 가장 기본적으로는 CCTV, 동작 감지기 등의 가게 주변의 환경의 변화를 모니터링 하고 알려주는 센서들을 설치 할텐데 왜 그러한 센서들을 설치하려 하는 걸까? 결국은 특정한 상황을 알려주는 데이터를 얻기 위해서 라고 볼수 있을 것이다.

 

  만약 CCTV 가 적절하지 않은 장소에 설치된다면 어떤 일이 벌어지게 될까? 잘못되거나 의미 없는 데이터를 가지고 판단(또는 모니터링) 하는 결과를 가지게 될 것 이다. 또한 현실적으로 사람이 잠시도 빼먹지 않고(또는 지치지 않고) 모니터링 하는 모든 데이터들을 100% 보고 있을 순 없기 때문에 여러가지 데이터에 여러 소프트웨어 적인 요소들을 적용하여 이상 현상을 찾으려고 한다. CCTV 를 예로 들면 화면의 변화, 화면안의 객체의 움직임, 해당 움직임의 의미 등을 프로그래밍(나아가 데이터의 통계나 구조적인 의미를 알려주는 머신러닝)을 통해 해석해서, 침해가 일어난 다고 의심되는 특정 이벤트 만을 알람으로 발생하게 하여 효율적인 모니터링을 하려고 한다. 이러한 관점을 생각하게 되면 물리적 모니터링은 소프트웨어 적인 모니터링과 문제의 성격으로 보아 별 차이는 없어 보이게 된다.

 

  또한 이러한 데이터 부분은 현실적인 행동이 있어야만 의미가 있을 수도 있다. 경험상 가게 자체적인 CCTV 의 구축이 효율이 적은 이유 중 하나는 누군가 계속 제대로 모니터링을 해야하며, 사건이 발생했을 때 조치할 방법이나 즉각적으로 행동할 사람이 없다면 효용성이 많이 떨어지기 때문이다(몇일 치의 CCTV 를 뒤지면서 원하는 장면을 찾아본 경험이 있다면 사후 조치란 의미로 원인을 찾기 것이 얼마나 피곤하게 만드는 일인지 알수 있을 것이다). 이러한 부분이 보호해야할 자산을 가진 사람들이 중앙 집중적인 관제 및 조치하는 사람이 있는 서비스를 제공하고 있는 **원 같은 보안 서비스 들을 사용하는 이유라고 생각된다. 해당 서비스에서 이상적으로 영상 및 이벤트 데이터 들은 실시간으로 원격에 저장되어 안전하게 보존, 백업 되며, 각 이벤트 들은 관제사 및 프로그램들에 의해서 체크 되며, 문제가 있을 시 물리적 조치를 행사할 수 있는 인원들이 움직이기 때문일 것 같다. 물론 실제로는 상징적인 효과일 수도 있고 비용이나 프라이버시 문제가 있을 수도 있을 것이다.

 

 

 

 

2. 결국 중요한 것은 데이터

  이 글의 맨 처음에서 보안의 주요한 부분중 하나는 데이터의 흐름을 따라다니는 일 이라고 얘기 했었는데, 모니터링도 크게 그 범위를 벗어나지는 않다고 본다. 앞의 리버싱과 포렌식 글에서 컴퓨터는 0과 1로 이루어진 세계로 얘기 했는데, 점점 시간이 지날 수록 현실의 많은 데이터는 이 0과 1로 이루어진 형태로 등가적으로 변형되어(디지털 화) 컴퓨터 안에 들어가고 있다.

 

 

  CCTV 의 영상도, 다른 여러 센서의 데이터도, 사람들의 행동들도 모두 디지털화 됨으로서, 어떻게 보면 현실의 많은 부분들이 이젠 컴퓨터내의 문제로 등가적으로 치환되었다고 볼수 있을 듯하다. 그렇게 컴퓨터 내의 데이터 문제가 되어, 그 동안 사람들이 고안해낸 여러 컴퓨터 내의 데이터 문제를 해결하는 기법들을 사용할 수 있게 됬지만, 그렇게 됨으로서 몇 가지의 새로운 차원의 문제도 발생하게 되었다고 본다.

 

 

  첫째로 정확하게 현실의 특징을 충분히 반영하여 디지털로 변환 되었냐는 문제가 있다. 예를 들어 예전의 해상도가 낮았던 시절의 CCTV는 나중에 해당 데이터를 가지고 100% 정확한 판단을 하기 힘든 문제가 있었다. CCTV 위치가 잘못 되어 태양 빛이 너무 밝게 들어와 영상을 제대로 인지 못했을 수도 있고, 센서가 고장날 수도 있으며, 센서의 설계가 처음부터 적합치 않았을 수도 있다.

 

  이건 소프트웨어 보안 쪽에서도 마찬가지 라고 보는데, 우리는 모든 데이터가 로그나 데이터베이스의 형태로 쌓였다고 생각하지만, 실제 그 데이터를 일으키게 한 대상은 컴퓨터 바깥 세상의 존재일 가능성이 높다. 그럼 그 데이터를 만들어낸 대상이 가진 특징을 올 해당 데이터를 기준으로 올바르게 판단 가능할 정도로 정확히 가지고 있는가를 우선적으로 따져봐야 한다. 영화에서 나오는 CCTV 나 센서를 보고 있는 감시 요원들이 주인공을 놓치게 되는 이유는 이 판단을 위한 원천 데이터 자체가 왜곡되는 경우라고 볼수 있을 것 같다. 

 

  혹은 아예 처음 부터 소프트웨어에서 자체에서 생겨난 데이터일 수도 있겠지만 그 경우도 우리가 최종 모니터링에 사용할 기반 데이터를 제대로 만들어낸 것인 지에 대해서 항상 여러 측면에서 고민을 해봐야 하는것 같다. 그래서 데이터를 만들어낸 도메인을 제대로 이해한 상태에서, 데이터의 수집부터 판단에 필요한 데이터를 만들어 내고, 위의 소프트웨어 적인 해결 도구들을 적절히 사용했는지를 잘 검토해야 하는 것 같다.

 

 

  둘째로 첫번째와 비슷하지만 우리가 현재 데이터라고 믿고있는 기준이 실제 우리가 원하는 현상을 모니터링 하기에 충분하냐는 문제가 있다. 세상이란게 실제로 많은 부분 근사와 추정으로 이루어져 있긴 한듯 하지만, 가능한 현재 모니터링 하고 싶은 부분에 대해 적절한 이벤트를 만들어 낼 수 있는 데이터를 수집하고 있는지는 체크해 봐야 한다.

 

  보안이나 QA가 좀 그런 면이 있긴 하지만, 시스템을 움직이는 데이터와 시스템을 모니터링 하는 데이터는 겹칠때도 많지만 별개일 수도 있다. 시스템의 여러 동작 간에 모니터링을 위한 데이터를 일부러 쌓아야 하는 경우도 있고, 뒤에서 천천히 집계하여 효율성 있게 모니터링 하기 위한 기반 데이터를 만들어야 하는 경우도 많아 보인다. 이러한 부분은 저장 비용, 퍼포먼스와도 밀접히 연관되어 있는 경우도 많으므로, 뻔한 얘기긴 하지만 새로운 시스템이 만들어지고 적절한 보안적인 모니터링이 필요하다면 설계 단계 부터 여러 측면에서 설계나 예산 측면에서 고려를 해야 하는 부분 같다. 일단 시스템이 본 궤도에서 돌아가게 되면 모니터링을 끼워넣기는 엄청 힘들어지는 것 같다.

 

 

  셋째로 과거의 데이터를 기반으로 만들어낸 규칙이 새로운 데이터에 얼마나 적합한지에 대한 부분이다. 머신러닝에서도 학습된 모델이 현재 얼마나 유효한 지에 대해 항상 고민을 하긴 하지만, 굳이 그렇게 복잡한 상황이 아니더라도 기본적인 모니터링 판단에서도 마찬가지다.

 

  만약 CCTV 를 열심히 설치해 놨는데 새로운 문이 생기면 어떻게 될까? 새로운 권한을 가진 사람들이 같이 근무하게 될수도 있을 것이다. 회사의 근무시간이 고정된 시간에서 자율 출퇴근 제로 바뀌어도 마찬가지 일것이다. 또한 재택근무가 된다면 등등... 현재의 고정된 판단을 가졌던 데이터의 규칙이 언제라도 특정 시점에 바뀔 수 있을 것이다. 기존 모니터링 시스템에서 받아들이 데이터 들의 그런 변화를 적절히 2차 모니터링 하여 데이터의 대상 및 룰에 대한 변화를 알려 줄수 있는 부분도 필요할 수 있다. 물론 그렇게 변할 가능성이 있는 데이터를 판단의 기준으로 삼지 않는 접근 방법도 있지만, 당장 효과가 있는 판단 기준들을 쉽게 빼는 것은 쉽지 않은 일이다. 애시당초 부터 적절한 판단을 할 수 없을 지도 모르고 말이다.

 

   여러 현실의 트랜드나 구조의 변화 때문에 생성되는 데이터의 성격이 충분히 변할 수 있기 때문에 이러한 변화를 모니터링 하는 부분도 역설적으로 모니터링을 구축하는데 또 다른 숙제가 되는 듯하다. 

 

 

  넷째로는 데이터가 조작에 얼마나 취약하느냐를 따져봐야 한다. 보안 쪽 모니터링을 하면서 데이터가 항상 주어진 그대로의 특성을 유지할 것이라고 믿는 것은 피해야할 시각이다. 앞에서 얘기한 인젝션 같은 문제로 기대하지 않은 데이터가 들어와 순진한 프로그램을 악용하는 것처럼, 순진한 모니터링 프로그램은 조작된 데이터를 그대로 놓쳐버릴 수 있다. 특히 외부의 움직임에 의해 생성되어 들어오는 데이터를 기반으로 모니터링을 할때는 항상 이런 부분을 더 신경써야 하는 것 같다. 

 

 

  다섯째로 만들어진 모니터링 프로그램은 대부분 명확한 답이 없이 "임계점"을 기준으로 Alert 을 띄우는 경우가 많으므로, 해당 임계점을 어떻게 조정하느냐도 민감한 주제가 될것 이다. 좀더 확실히 놓치지 않기위해 임계점을 낮추게 되면 수많은 Alert 의 늪에 빠지게 될것이고, 운영의 효율을 위해서 높이게 되면 삶의 질은 나아지겠지만 문제를 놓치게 될 가능성이 높아지기 때문이다. 

 

  마지막으로 결국은 모니터링은 사람의 문제로 귀결된다는 거다. 그건 모니터링 하는 입장이나 해당 처리를 하는 입장에 모두 해당 된다. 아무리 잘 만들어진 시스템이 많이 이벤트와 좋은 대시보드 화면을 보여준다고 해도 결국 최종적으로 오탐인지, 위험이 있는지 판단하는 부분은 사람일 수 밖에 없다. 또한 많은 부분에서 주어진 데이터에 대해 알고있는 도메인 지식을 기반으로 해석해야만 정상적인 판단을 할 수 있는 경우도 많다. 

 

  사람이란게 필연적으로 먹고 자는 존재이며, 기분에 따라 컨디션이 많이 달라질 수 있는 존재이기 때문에, 그러한 부분에 따른 판단 오차 및 대응 지연을 최소화 시킬 수 있는 시스템 및 모니터링 리소스의 배치도 마찬가지로 필요한 듯 싶다. 시스템 적인 입장만 생각하자면 최종 결과를 메일, SMS, 메신저 등의 연락처로 보내고 관련된 데이터와 그래프를 보여주면 된다고 생각할 수 있지만 그 메시지와 데이터를 보고 판단하는 사람들이 처한 환경을 간과 해서는 안된다. 물론 그러한 판단을 모니터링 하는 사람들이 힘을 덜 들이면서도 정확하고, 오차없게 하게 하는 여러 노력들은 필요하겠지만 말이다.

 

  때로는 100% 자동으로 이루어지는 선처리도 있긴 할테지만(예를 들어 과열시의 차단), 어쨋거나 해당 원인을 밝혀 개선하거나, 적절한 차단인지 되돌아 검토하거나, 룰을 수정하는 등의 일은 사람의 판단이 결국 필요하게 된다. 그래서 모니터링은 어찌보면 데이터의 생성 방식부터 최종 판단 까지 전체를 물리적, 디지털 적으로 잘 케어해야 하는 종합적인 분야같다.  

 

 

 

 

3. 간단한 모니터링 예제 보기

  어떤 예제를 보여줄까 고민하다가, 결국은 데이터의 한 측면을 보고 판단을 하는 예제를 간단히 보여주는 것으로 결정했다.

 

  우선 모니터링 대상은 파이썬으로 웹페이지를 하나 만들며 해당 페이지는 호출 할때마다 1부터 하나씩 숫자가 증가하면서 해당 숫자를 <td> 태그 안에 넣어 보여준다. 다음은 모니터링을 하는 프로그램인데, 해당 페이지를 1초 마다 한번 호출 하면서 5가 발견되면 SQLite 디비에 해당 결과를 저장 후, 화면에 읽어 표시한다.

 

 

3.1 모니터링 대상 페이지 만들기

  우선 숫자를 표시해 주는 flask 웹 페이지를 보자. 내용을 보면 global 변수를 이용하여 계속 0부터 1을 증가 시키면서 monitoring.html 을 랜더링 하면서 해당 값을 웹페이지에 표시해준다(코드가 이해 안가는 경우는 파이썬 플라스크 편을 보고 오자).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, make_response, render_template
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
num_count = 1
 
@app.route("/monitoring", methods=['GET'])
def xss():
    global num_count 
    count_string = str(num_count)
    num_count = num_count + 1
    return render_template('monitoring.html', count = count_string)
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

 

   해당 파일을 c:\python\code 폴더에 "flask_monitoring.py" 이름으로 "UTF-8" 포맷으로 저장한다.

 

 

  다음은 해당되는 템플릿 파일을 보자. 정말 간단하게 <td> 태그 하나만 하나 있다. class 이름으로 check_point 를 지정한 이유는 나중에 beautifulsoup 라이브러리로 가져올때 td 태그가 여러개 있을 때 기준을 가지고 쉽게 가져오기 위해서이다.

1
<td class="check_point">{{ count|safe }}</td>
cs

 

   c:\python\code\templates 폴더에 "monitoring.html" 이름으로 "UTF-8" 포맷으로 저장한다.

 

 

  이제 커맨드 창에서 실행 시켜 페이지가 잘 동작하나 본다.

c:\Python\code>python flask_monitoring.py
....
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

 

  브라우저로 웹페이지를 띄워 리프레시를 해보면 호출할 때마다 1씩 숫자가 증가됨을 볼 수 있다.

http://localhost:5000/monitoring

 

 

 

3.2 모니터링 대상 페이지 만들기

  이제 해당 페이지를 모니터링 하는 프로그램을 만들어 보자. 해당 프로그램은 http://localhost:5000/monitoring 페이지를 30번까지 호출 하며, class 가 check_point 인 <td> 태그의 내용을 가져와 5를 발견하게 되면 로컬 SQLite 디비에 이벤트와 시간을 저장하고 for 루프를 먼춘 후, 해당 디비에 저장한 내용을 읽어와 화면에 표시한다.  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import sqlite3
import requests
from bs4 import BeautifulSoup
import datetime
import time
 
 
# sqlite 파일을 열음
conn = sqlite3.connect("monitoring.db", isolation_level=None)
cursor = conn.cursor()
 
# BookInfo 테이블을 생성한다.
cursor.execute("""CREATE TABLE IF NOT EXISTS SiteInfo(checkDate timestamp, checkNum text, result text)""")
 
# url 요청할 세션 만들기
= requests.session()
 
# URL 만들기
searchurl = 'http://127.0.0.1:5000/monitoring'
 
# 30번 루프를 돈다. 
for i in range(30):
    # URL 호출
    con = s.get(searchurl)
 
    # html 파서 사용
    soup = BeautifulSoup(con.text, 'html.parser')
 
    # 숫자가 들어있는 태그 가져오와 표시해 보기
    check_num = soup.find("td", class_="check_point")
    print(check_num.string)
 
    # 5가 발견 됬다면
    if check_num.string == "5":
 
        # 발견했다 출력하고 테이블에 저장하기
        print("find it: " + check_num.string)
 
        sql = "INSERT into SiteInfo(checkDate, checkNum, result) values (?, ?, ?)"
        cursor.execute(sql, (datetime.datetime.now(), check_num.string, "find it"))
        break
    else:
        # 못 찼았다 출력 하기
        print("no event")
    
    # 1 초 쉰다
    time.sleep(1)
 
# 테이블에 저장한 값 불러오기
sql = "select checkDate, checkNum, result from SiteInfo"
 
cursor.execute(sql)
row = cursor.fetchone()
 
if row: 
   while row:
      print(row)
      row = cursor.fetchone()
cs

 

  c:\python\code 폴더에 "flask_monitoring.py" 이름으로 "UTF-8" 포맷으로 저장 후 커맨드 창에서 실행해 보자(아래와 같이 1부터 다시 나오게 하려면 앞의 웹 서버 실행을 취소했다 다시 시작하면 된다)

 

c:\Python\code>python monitoring_job.py
1
no event
2
no event
3
no event
4
no event
5
find it: 5
('2019-10-13 20:48:09.088506', '5', 'find it')

 

 

 

 

4. 마무리 하면서

  사실 예제처럼 웹 호출을 기반으로 데이터를 가져와 모니터링 하는 것은 마이너한 경우일 테지만, 꼭 가져오는 방식이 중요한 것은 아니다. 웹에서든, 디비든, 장비든, 로그 파일이든 결국은 파이썬 1교시의 프로그래밍의 입력과 같이 형태만 다를뿐 결국 파싱해 가져오고 싶어하는 건 최종적으로 데이터라는 데엔 변함이 없다. 모니터링은 과감히 간략화 하자면 컴퓨터 내에 적재한 여러 데이터를 기반으로 적절한 체크 포인트를 찾아서, 특정한 데이터의 변화가 일어났을때 알려주며, 종종 그래프 같은 시각적인 형태로 모니터링하는 사람에게 데이터의 추이를 설명해주는 단순한 작업이다.

 

 

  생각보다 모니터링 업무를 하는 사람에게는 지루하거나 스트레스를 받는 일이며, 발생하는 이벤트를 수동적으로 계속 따라가게 됨으로서, 그다지 생산적이지도 않게 느껴진다. 일적으로도 대상을 지키는 업무라는 인식이 강하기 때문에 하는 일의 중요도에 비해 많은 인정을 받지도 못하는 경향도 있다. 때로는 자신의 메인 업무에 부록처럼 따라오는 시간을 조금씩 갏아가는 귀찮은 일일 수도 있다. 또한 운영 업무의 대부분은 이런 모니터링과 일부 또는 전부 연관되어 있다.

 

  반면에 모니터링을 정확히 잘하려면 대상 데이터를 정확히 이해해야 하며, 데이터를 정확하게 이해하기 위해서 데이터를 만들어내는 전반적인 도메인과 관련된 시스템, 사물, 사람의 행동을 이해해야 하기 때문에, 깊이 들어가고자 하면 생각보다 복잡한 일이기도 하다(예를 들어 SQL 을 모니터링 하려면 기본적인 SQL 문법과 사용자 들이 사용하는 패턴의 이해, 해당 SQL의 대상이 되는 서버, 디비, 테이블, 컬럼의 성격을 이해해야만 한다). 뭐 하지만 이런 부분은 IT를 포함한 어떤 분야의 일이나 마찬가지인듯 하다. 다들 익숙하게됨 지루하게 보자면 무척 지루한 일일 수도 있지만, 의미를 가지고 데이터의 원천에 관심을 가진다면 다른 차원의 일로도 볼수도 있다. 스스로 모니터링 시스템을 설계하거나 개선하는 업무의 롤이라면 더 더욱 그럴 것이고 말이다.

 

  모니터링 업무를 도메인의 데이터를 이해하고 적절하게 데이터를 해석할수 있는 툴을 적용하는 일이라고 정의해 보면 어떨까 싶다. 그러면 보안의 다른 분야와 마찬가지로 무척 공부할 것이 많아지는 듯 싶다.

 

 

2019.10.14 by 자유로운설탕
cs

 

 

 

 

 

 

 

 

 

 

 

 

posted by 자유로운설탕
2019. 8. 25. 17:34 보안

  이번 시간에는 리버싱(리버스 엔지니어링: Reverse Engineering)과 포렌식(Forensic)에 대해서 이야기 해보려 한다. 시작하기 전에 먼저 양해를 구하고 싶은 부분은 이 두 분야에 대한 실무를 해본적이 없기 때문에 그다지 자신이 없다. 다만 어떻게 이 두 분야를 바라봐야 할까에 대해서 간단한 예제들를 중심으로 이야기를 풀어보려 한다.



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터


 


 


1. 들어가면서

  위의 두 개의 큰 주제를 하나의 파트로 묶은 이유는, 결과적으로 생각했을 때 두 분야의 연결점이 무척 많다고 생각하기 때문이다. 둘 중 한 쪽 분야에 대한 탄탄한 지식은 다른 분야를 접근 할때 허들을 많이 낮출테고, 일을 함에 있어서도 공통이 되는 지식 분야가 분리하기 힘들 정도로 많이 겹친다고 본다. 비유하자면 포렌식이 생태계과 생물들이 살아갔던 흔적에 주로 관심이 있다면, 리버싱은 생태계 안에서 살아가는 생물들의 행동 관찰에 관심이 있는 분야라고 본다. 생물을 이해하려면 생물들이 살아가는 환경을 이해해야 하고, 환경의 변화를 추적하려면 환경안에서 살아가는 존재들의 상호작용을 이해해야 할 것이다.

 

  사실 그것은 보안 전체에 대해서도 마찬가지인 부분 같다. 편의상 여러 분야로 나누어서 보안 지식을 분류하긴 하지만, 크게 보면 모든 분야가 유기적으로 얽혀 있다고 볼 수 있기 때문에, 어디나 분위기는 비슷비슷 하다고 본다. 반대로 여러 분야의 지식이 어느 정도 골고루 쌓여있지 않다면 뭔가 구멍이 뚫려 있듯이 어설픈 상태에 놓였다는 느낌이 들게 된다. 개인적으로 이 두 부분은 스스로 그렇게 느끼고 있는 구멍나 있는 부분들이기도 하다^^ 

 

 

 

 

2. 리버싱 예제 만들어 보기

  어딘가에서 만들어 놓은 샘플을 하나 가져와도 좋겠지만, 좀 더 간단하지만 통제가 되고 이해가 되는 예제를 예로 들기 위해서, 직접 C++ 로 프로그램을 하나 만들어서 비주얼 스튜디오의 정적 환경(뭐 디버깅 중이라서 동적이라고 볼 수도 있겠다)과 디버거 상에서 동적으로 실행되는 2개의 환경에서 코드를 비교해 볼까 한다. 윈도우즈 환경이기 때문에 요즘은 무료로 제공되고 있는 비주얼 스튜디오 커뮤니티(Visual Studio Community) 버전을 설치해 보도록 한다.

 

 

  구글에 "visual studio community" 라고 적으면 아래의 다운로드 링크를 찾을 수 있다.

 

[visual studio community - microsoft 홈페이지]

https://visualstudio.microsoft.com/ko/vs/community/

 

 

  다운로드 하여 실행하면 설치 화면이 나오는데, 설치를 진행하면서 Workload 탭 에서 "desktop development with c++" 기능을 체크해서 설치한다(이미 설치한 상태라서 스크린 샷은 못찍었는데 아래의 블로그를 참조하면 비슷한 듯 싶다). 뭐 다른 언어도 이것저것 공부할때 필요할 듯 하다면 설치해도 좋다.

 

[비주얼 스튜디오 2017 커뮤니티에서 c/c++ 프로그래밍 하기 - 모두의 코드님 블로그]

https://modoocode.com/220

 

 

 

2.1 리버싱 예제 만들기

  이후 프로그램을 실행을 시키고, "새 프로젝트 만들기" 를 선택한다. 위쪽 검색 메뉴에서 "c++" 를 입력하면, 아래와 같이 c++ 로 콘솔 앱을 만드는 항목이 보이게 된다. 해당 항목을 선택하고, "다음" 버튼을 누른다.

 

 

  프로젝트 이름을 "ReversingSample" 로 위치를 매번 파이썬 파일을 만들던 "c:\Python\Code" 로, 솔루션 이름을 "ReversionSample" 로 한 후, "만들기" 버튼을 누른다(나중에 디버거 툴로 선택할 exe 파일을 잘 찾을 수 있다면 기본값으로 설정 하거나 임의로 생성해도 된다)

 

 

  아래와 같이 정말 간단한 샘플 코드가 만들어져 나오는데, 간단히 기본 커맨드 화면에 "hello world" 를 뿌려주는 코드다(파이썬 코드의 print("hello world\n") 문이 하나 있다고 보면 된다)

 

코드 부분만을 분리해 보면 아래와 같다.

1
2
3
4
5
6
#include <iostream>
 
int main()
{
    std::cout << "Hello World!\n";
}
cs

 

 

 

  위의 코드 자체에서 바로 살펴봐도 되지만, 그러기에는 뜯어 볼 게 너무 없는 코드이므로, 해당 내용을 모두 지우고 아래와 같이 for 문을 돌면서 Hello World 문구를 4번 입력하는 코드로 대체해 보자. 마지막에는 출력후 커맨드 창이 바로 종료되지 않도록 getchar() 함수를 넣어 사용자가 글자를 하나 입력해야 종료되도록 만들었다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
    for (int count = 0; count < 4; count++)
       printf("hello, world\n");
 
    getchar();
    return 0;
}
cs

 

 

 

2.2 정적인 코드 보기

  정적인 분석을 하기 위해서 이제 화면에서 c++ 코드에 해당 하는 어셈블리 코드를 봐보자. 원래는 cl.exe 라는 명령어를 사용해서 컴파일을 해서 보는 방법을 많이 쓰는 거 같은데, 구글을 찾다보니 IDE UI 에서 편하게 볼 수 있는 기능이 있어서 해당 방식으로 보려 한다.

 

  아래의 그림과 같이 for 문 코드의 왼쪽 공간의 영역에서 마우스 왼쪽 버튼을 클릭해 빨간 브레이크 포인트를 찍는다. 이후 위쪽 Debug 드롭박스 옆의 환경을 64비트 어셈블리 코드를 보기위해서 "x86 -> x64" 로 바꾼다. 이후 그 옆의 "로컬 Windows 디버거" 버튼을 눌러서 디버깅 모드로 들어간다.

 

 

  그러면 컴파일 및 빌드가 진행 된 후, 아무것도 표시되지 않는(브레이크 포인트 땜에 아직 for 문이 돌아기지 못했으므로... 피들러에서 브레이크 포인트를 건 것과 비슷하다) CMD 창이 하나 뜨면서, 비주얼 스튜디오 창의 브레이크 포인트를 찍은 for 문 코드에서 멈춰 있게 된다.

 

  이 후, 해당 for 문 코드 위에서 마우스 오른쪽 버튼을 누르면, "디스어셈블리로 이동" 이라는 메뉴가 보이게 된다. 해당 메뉴를 클릭하면, 탭이 하나더 열리면서 현재 코드에 해당하는 어셈블리 코드가 보이게 된다(마치 브라우저에서 요소검사를 했다고 생각해도 될듯 싶다).

 

 

  그럼 위와 같은 처음 보게 된다면, 조금은 암호와 같이 느껴질수 있는 코드가 보여지게 된다. 화면을 보면 코드를 비교해 편하도록 기존 c++ 코드를 흰색 글자로 보여주고, 그에 해당하는 어셈블리 코드를 회색 코드로 보여주고 있다. 어셈블리 코드 쪽을 문법을 보면 크게 3가지의 섹션으로 나누어 지는데, 첫번째 섹션이 메모리 주소(00007FF... 같은거), 두번째 섹션이 명령어(mov, jmp 같은거), 세번째 섹션이 인자(eax)라고 보면 된다.

 

  컴파일을 하게 되면 0과 1로만 이루어진 기계어로 된 코드가 만들어 지는데 해당 코드가 CPU 쪽으로 전달되게 되어 정해진 명령어들을 수행하면서 컴퓨터가 동작하게 된다. CPU 를 0과 1로 이루어진 코드를 이용해 움직이게 하는 부분이 상상이 안간다면 파이썬 글에서도 추천했던 아래와 같은 책을 한 권 읽어보길 권한다.

  • CODE 코드 : 하드웨어와 소프트웨어에 숨어 있는 언어

 

  그런 0과 1로 된 코드를 보면서 동작을 상상하는 것은, 사람들에게는 낯설기도 하고 코드도 너무 복잡해 져서 하기 힘든 일이기 때문에, 조금 더 CPU 와 상호 작용하는 부분을 동작에 따라 분류해서 사람이 읽기 쉽도록 정리한 언어가 어셈블리어이다. 지금은 일반적으로는 고대어 처럼 취급되긴 하지만, 처음 컴퓨터가 개발됬을 때에는 기계어가 지금의 c 언어 정도이고, 어셈블리어가 자바나, Python, NET 같은 사람에게 가독성과 프로그래밍을 쉽게 하도록 만들어주는 언어 였을 것 같다. 프로그래머들은 아마도 기계어를 안봐도 되는, 어셈블리 언어가 있는게 무척 고마웠을 것이고 말이다. 그런 어셈블리 언어를 다시 한번 감싼게 c, c++ 같은 언어이고, 그 것을 한번 더 추상화 한게, Java 나 Python 같은 언어일 것이다.

 

  현재는 프로그래밍 분야에서는 컴파일러의 동작을 배우거나, 임베디드 프로그래밍을 하지 않는 이상 어셈블리어를 배울일은 거의 없을 것이다(위키를 보다보니 요즘은 사물인터넷 때문에 작은 하드웨어에 최적화된 프로그래밍을 하기위해서 어셈블리어가 다시 사용이 늘고 있다고는 한다). 파이썬의 numpy 같은 특정 모듈이 성능을 위해 c 언어로 개발됬듯이, 종종 c 프로그램의 성능과 효율성을 높이기 위해서 특정 모듈을 직접 어셈블리어로 개발하는 경우도 있다곤 하지만, 꽤 드문 일일 것같다. 그래서 특정한 분야에 일하지 않고는 해당 언어를 사용하거나 볼 수 있는 기회가 드물다는게 어셈블리어를 배우는데 동기부여가 힘든 부분 중 하나인것 같다. 예를 들면 어떤 사람들에게는 평소에 전혀 안 쓰는 예전의 라틴어 같은 언어를 배우는 거와 비슷한 일이 되버릴 수 있다.

 

 

  또한 어셈블리어와 기계어는 1:1 의 매칭이 될테지만, 상위 수준의 언어와는 1 대 多, 또는 多 대 1의 관계가 될 수도 있다. 예를 들면 고 수준 언어로 갈수록 객체 지향 같은 구조적인 프로그램밍이나 코딩의 편의를 위해서 생긴 여러가지 추상적인 요소가 있을 수 있겠지만, 컴파일이 되어 실제 실행을 하는 기계어 코드 입장에서는 그런 사람을 위한 머리 꼬리들은 모두 띄어내고 실행에 필요한 요소들만 코드화 하게 할 것이다. 그렇게 됨으로서 한번 컴파일 된 언어는 다시 돌아갔을때, 원래 언어의 모양 그대로 복원하기 힘들게 되는 일이 생겨버리게 된다(아주 예쁘게 데코한 케잌을 일하면서 한 손으로 효율적으로 먹기위해 꽁꽁 뭉쳐버렸다고 생각해 보자. 원래 모양에 대한 정보가 없기 때문에 완전한 복원은 힘들다--;).

 

  또 자바나 Python 같은 언어를 사용해 보면 c 같은 언어에 비해서 사용자 코드만 만들면, 나머지 모든 것은 자동으로 연결해 움직이게 해주는 경향이 있다. 그러다 보니 실제 코딩을 한 코드가 빌드 될때, 기계어로 바뀐 후, 여러 미리 만들어진 외부 코드를 링크 하여 사용하기 때문에, 실제 기계어 입장에서 실행되는 코드는 처음 코드보다 몇 배는 부풀려져 있다. 보통 디버거를 돌릴때, 우리가 찾으려는 코드에 닿기 전에, 앞에 OS에서 움직이기 위해서 필요한 여러 코드(또는 디버깅을 방해하기 위한 방어코드)의 숲을 빠져나오는 여러가지 요령을 배우게 된다. 

 

  IDE 의 Hex-Rays 라는 플러그인을 사용하면 어셈블리어가 좀더 가독성이 높은 c++ 로 보여진다고는 하지만, 밑의 블로그에서 볼수 있듯이, 사람이 만든 변수명 등의 추상적인 정보가 다 기계어 쪽에 남아있진 않기 때문에 복원이라기 보단 어셈블리어가 문법만 c++ 로 보이도록 재구성 했다고 보는게 맞을 듯 싶다. 그리고 어째거나 c++ 도 엄청 잘해야 해당 코드를 잘 볼수 있을 테고 말이다.

 

[Reversing C++ programs with IDA pro and Hex-rays - ARIS's Blog]

https://blog.0xbadc0de.be/archives/67

 

  자바같이 여러 환경에서 자바 가상환경만 설치하면 그 위에서 호환되어 돌아가는 언어는, 컴파일을 하면 기계어가 아닌 가상환경에서 해석되는 공통 코드로 만들어지기 때문에 원래의 자바 언어로 디컴파일이 좀더 쉬운 편이다. 하지만 뭐 항상 그렇지만 코드는 점점 복잡해 지고 있고, 해당 언어를 아주 잘하지 않는 이상 남이 만든 코드를 보고 주어진 시간내에 이해하는 건 쉬운일은 아닌 것 같다.

 

[컴파일러 - 나무위키]

https://namu.wiki/w/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC

 

 

 

  사족이 길었지만 다음 이야기를 진행하기 전에 프로그램의 리버싱에서 왜 어셈블리어를 사용해야 하는지를 이해하고 넘어갔음 해서 그랬다. 그럼 비주얼 스튜디오에서 만든 코드를 보면서 이미 알고 있는 c++ 코드에 기반해서 어셈블리 코드의 동작방식을 해석해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    for (int count = 0; count < 4; count++)
00007FF799ED184A  mov         dword ptr [rbp+4],0  
00007FF799ED1851  jmp         main+3Bh (07FF799ED185Bh)  
00007FF799ED1853  mov         eax,dword ptr [rbp+4]  
00007FF799ED1856  inc         eax  
00007FF799ED1858  mov         dword ptr [rbp+4],eax  
00007FF799ED185B  cmp         dword ptr [rbp+4],4  
00007FF799ED185F  jge         main+4Fh (07FF799ED186Fh)  
        printf("hello, world\n");
00007FF799ED1861  lea         rcx,[string "hello, world\n" (07FF799ED9C28h)]  
00007FF799ED1868  call        printf (07FF799ED11D1h)  
00007FF799ED186D  jmp         main+33h (07FF799ED1853h)  
    getchar();
00007FF799ED186F  call        qword ptr [__imp_getchar (07FF799EE02F0h)]  
    return 0;
00007FF799ED1875  xor         eax,eax  
}
cs

 

  실제 편집창을 보면 저 잘라낸 코드 말고도 전체 코드는 엄청 길지만 앞뒤의 코드는 앞서 말한 "윈도우즈 환경에서, 커맨드 창을 통해서 해당 코드가 실행하기 위한 행사코드(행사 코드는 개발자인 임백준씨의 표현인데 이 경우 적절한 표현같다)" 라고 생각하면 될것 같다.

 

  다음 부터 나오는 모르는 어셈블리 키워드 들은 구글에서 "어셈블리 eax" 등으로 찾아 퍼즐을 맞춰보면 된다. 코드를 보면 rbp, eax, rcx같은 애들이 나타나는데, 이들은 모두 레지스터를 나타낸다. 레지스터는 CPU 입장에서 보면 프로그램의 변수와 같다. 이 변수들에 메모리에 있는 주소 번지, 숫자들과 잘 조합해서 명령을 실행한다.

 

  rbp, rcx 같은 경우는 64비트에서만 해당 표현을 볼수 있는 레지스트로 만약 앞에서 x86 으로 드랍박스를 선택했다면 나오지 않을 레지스트 이름이다(x86에서는 ebp, ecx등으로 e 로 시작되게 지칭한다). 앞으로 코드를 보면 알겠지만 64비트에서는 rcx 도 32비트에서의 동급인 레지스트인  ecx 표현으로도 같이 사용한다(코드 흐름상 추측하면 64비트에서는 rcx 사용 공간을 반만 사용하면 ecx 같다). 각 변수들은 미리 잘 쓰는 용도를 정해 놓은 편인데, 그건 어셈블리 코드를 많이 보면서 패턴상에서 감으로 익혀야 될 문제 같다.

 

   2번째 라인을 보면 "mov  dword ptr [rbp+4],0" 이 있다. 대충 살펴보면 rbp 라는 메모리 스택의 주소를 관리하는 레지스트가 가르키는 스택 위치에 0을 넣는다. 위에서 보면 c++ 코드의 "for (int count = 0; count < 4; count++)" 의 count=0 에 해당하는 주소 같아 보인다. 이후 3번째 줄의 "jmp   main+3Bh (07FF799ED185Bh)" 의 점프(jump=jmp) 명령을 통해 해당 주소인 7번째 줄(07FF799ED185B 주소)의 "cmp   dword ptr [rbp+4],4" 로 간다. 보면 아까 넣어놨던 0을 4와 비교(compare=cmp) 하는 라인이다.

 

  그 다음 8번째 줄을 보면 "jge main+4Fh (07FF799ED186Fh)" 로 앞 줄에서 비교한 결과가 크거나 같으면 루프를 벗어나 다음 코드(07FF799ED186F 주소 - c++ 코드로 보면 getchar())로 점프하는(jump if great or equal = jge) 코드가 있다. 지금은 0과 4를 비교했기 때문에 당연히 만족이 안 될 것이므로 점프 하지 않고 9번 줄로 넘어 갈것이다.

 

  9 번째 줄로 가면, 문자열 "Hello World\n" 가 담긴 주소를 rcx 레지스터로 담는다(Load Effective Address = lea). 이후 10번째 줄에서 "call  printf (07FF799ED11D1h)" 를 통해 윈도우즈의 printf api 를 호출해서 화면에 "Hello World" 를 뿌린다. 이후 11번째 줄에서 다시 루프의 시작에서 실행이 안됬던 4번째 줄로 점프한다(jmp  main+33h (07FF799ED1853h)

 

  4번째 줄을 보면, 아까 스택에 넣었던 0값을 eax 레지스터로 옮긴 후(mov  eax,dword ptr [rbp+4]), 5번째 줄 "(inc  eax)" 에서 1을 증가(increment = inc) 시키고, 6번째 줄에서 다시 1이 더해진 eax 값을 아까는 0이 들어있던 스택 주소로 보내 값을 0->1로 업데이트 한다(mov  dword ptr [rbp+4],eax)

 

  이후 7번째 줄로 가면 다시 위에서 설명했던 비교 명령을 통해서 1과 4를 비교하게 된다. 이렇게 계속 반복되다보면 스택의 0 값은 0, 1, 2, 3, 4, ... 가 되고, 0~3까지는 계속 Hello World 를 4번 표시하다가 4가 담기면서 4보다 크거나 같다는(jge) 조건을 만족하게 되서, 비로서 루프를 빠져나가서 14번 줄의 "call  qword ptr [__imp_getchar (07FF799EE02F0h)]" 을 통해 getchar() API 를 호출하게 된다. 이후 16번 줄의 "xor  eax,eax" 을 통해(자기자신을 xor 하면 0이 되니까 뭐 대충 c++ 의 "retrun 0" 의 느낌이 난다) retrun 0 을 통해 메인 프로그램 함수를 종료하게 된다. 이렇게 저렇게 꼬인 점프 루프 부분만을 빼면 아래와 같은 그림으로 정리함 어떨까 싶다.

 

  이렇게 어셈블리 코드를 보게 되면 기존 고수준 언어랑 많이 다르다는 느낌이 든다. 절대 쓰지 말라고 권고하는 점프도 마음것 사용 하고, 무언가 빠른 실행 속도만을 위해 최적화된 코드의 느낌이다.

 

 

 

  그럼 마지막으로 한가지만 더 생각해 보자. 아래와 같이 다른 코드를 그대로 유지하면서, for 문의 2번째 "<" 만 "<=" 로 바꾸면 어떻게 될까?

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
    for (int count = 0count <= 4; count++)
        printf("hello, world\n");
    getchar();
    return 0;
}
cs

 

 

  유추해 보자면, 어셈블리어에는 jg (jump if greater than)라는 명령어가 있으므로, 기존 코드는 그대로 유지되면서 jge 가 jg 로만 바뀌면 한번 더 루프를 돌게 됨으로서 같아지지 않을까 싶다. 실제 코드를 수정하고 디버그 모드에서 어셈블리 코드를 보면 아래와 같이, jge 가 jg 로만 바뀐 어셈블리 코드가 나온다. 상황에 따라 두꺼운 책을 지루하게 보는 것보다는 이렇게 c 코드와 비교해 가면서 스스로 원본 코드의 난이도를 조정하면서 어셈블리 공부를 하는 것도 나쁘진 않을 것 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
00007FF7046C184A  mov         dword ptr [rbp+4],0  
    for (int count = 0; count <= 4; count++)
00007FF7046C1851  jmp         main+3Bh (07FF7046C185Bh)  
00007FF7046C1853  mov         eax,dword ptr [rbp+4]  
00007FF7046C1856  inc         eax  
00007FF7046C1858  mov         dword ptr [rbp+4],eax  
00007FF7046C185B  cmp         dword ptr [rbp+4],4  
00007FF7046C185F  jg          main+4Fh (07FF7046C186Fh)  
        printf("hello, world\n");
00007FF7046C1861  lea         rcx,[string "hello, world\n" (07FF7046C9C28h)]  
00007FF7046C1868  call        printf (07FF7046C11D1h)  
00007FF7046C186D  jmp         main+33h (07FF7046C1853h)  
    getchar();
00007FF7046C186F  call        qword ptr [__imp_getchar (07FF7046D02F0h)]  
    return 0;
00007FF7046C1875  xor         eax,eax  
cs

 

 

 

2.3 동적으로 보기

  이번에는 여러 리버싱 책에서 하는 것처럼, 디버기를 이용해서 같은 코드(다만 앞에 본것은 디버깅 빌드 버전이고, 이번엔 릴리즈 버전으로 만들어 본다. 디버깅에 필요한 사족이 다 제거된 버전이라고 보면 될 듯 하다)를 봐보자.

 

  우선 디버깅을 할 exe 파일을 만들어 내보자. 위쪽의 네모난 정지 버튼이나 "Shift+F5" 를 눌러 디버깅을 중지한다. 이후 아까 Debug 상태의 드롭박스를 "Release" 로 바꾸고, "빌드>솔루션 빌드" 명령어를 통해 빌드를 한다.

 

1>코드를 생성했습니다.
1>ReversingSample.vcxproj -> C:\Python\Code\ReversingSample\x64\Release\ReversingSample.exe

 

 

 

  이제 디버거 프로그램을 다운받도록 해보자. 구글을 찾아보니 요즘은 64비트 환경에서는

x64dbg 라는 프로그램을 많이 쓰는 것 같다. 아래의 페이지에서 zip 파일을 다운로드 받아서 적당한 폴더에 압축 해제 한다.

 

[x64dbg]

https://x64dbg.com/#start

 

 

  이후 release\x64 폴더내에 있는 x64dbg.exe 파일을 실행 시킨다. 디버거 창이 뜨면 "파일 > 열기" 명령어를 통해서 아까 만들어 놓은 C:\Python\code\ReversingSample\x64\Release 폴더에 있는, "ReversingSample.exe" 을 선택한다. 아래와 같이 디버깅이 시작되는 화면이 열린다.

 

 

  디버거는 프로그램이 시작되면서 cpu 에서 실행되는 명령어 들에 대해서 어셈블리 레벨에서 가로채서 보여주는 프로그램이다. 그러다 보니 특정 명령어를 찾아 건너 뛰거나, 프로그램에서 사용하는 여러 값을 변경하거나 하는 일을 자유롭게(잘 이해만 한다면) 할수 있다. 앞에서 얘기한 웹에서의 피들러로 조작했던 클라이언트 코드와 비슷하지만, 보다시피 어셈블리와 그 어셈블리와 상호작용하는 시스템 자체의 동작을 이해해야 그것이 가능하기 때문에 자유롭게 구사하기 위해서는 브라우저로 실행 공간을 추상화 시킨 웹보다는, 배우서 익숙해야 할 부분이 더 많이 필요하다고 생각한다. 다만 그 더 많이 필요한 부분은 보안 기술 자체의 난이도 라기 보다는 앞 시간들에서 얘기했던 대상을 이해하기 위한 난이도인것 같다. 어셈블리나 해당 어셈블리가 돌아가는 환경들을 이해하는 부분은 보안의 측면이라기 보다는 기본적인 IT 기술의 이해쪽에 좀 더 가깝다고 본다. 다시 한번 강조하지만 보안 쪽은 대상의 기술적 측면을 이해하지 못한다면 할수 있는 것이 아주 적어진다.

 

 

  아까의 비주얼 스튜디오 환경에서는 원하는 코드에 대응하는 어셈블리 코드를 바로 볼수 있었지만(브라우저에서 요소 검사를 통해 해당 코드 위치로 이동한 것과 비슷하다고 볼 수 있다), 실행 파일에서는 우리가 작성한 코드가 실행되기 전에, 윈도우 환경에서 프로그램이 실행되기 위한 여러가지 행사코드들이 먼저 실행 될 것이기 때문에 한줄 한줄을 실행하게 되면 한참을 따라 가야한다. F8(건너서 단계 진행) 같은 키를 눌러 나름 큰 발자국으로 한 단계씩 이동하다가 원하는 코드를 만날 수도 있겠지만, 그러다 보면 원하는 지점을 놓쳐 몇번 시행 착오도 겪을수 있으므로 디버거에는 특정한 문자열이나 명령어, 패턴 등의 어셈블리 코드 요소를 기준으로 원하는 위치를 찾을 수 있게 검색 기능이 제공되어 있다. 해당 코드 화면에서 마우스 오른쪽 버튼을 누르고 "다음을 찾기 > 모든 모듈 > 문자열 참조" 를 선택하면 아래와 같은 프로그램이 가지고 있는 문자열들을 리스팅 하는 화면이 나온다.

 

  오른쪽 문자열 팬에 보면 우리가 뿌려주는 "hello, world\n" 문자열이 보인다. 해당 항목을 더블 클릭 해보자. 자 그럼 아까 비주얼 스튜디오에서 봤던 코드와 비슷하지만 조금은 모양이 다른 코드가 보이게 된다.

 

 

  그럼 한번 코드를 살펴보자. 비주얼 스튜디오와 비슷한 모양이 되도록 16진수 코드가 나오는 중간 부분은 편집해 제거했다.

1
2
3
4
5
6
7
8
9
10
11
12
00007FF62C5F1070 | push rbx                               | 
00007FF62C5F1072 | sub rsp,20                             |
00007FF62C5F1076 | mov ebx,5                              | 
00007FF62C5F107B | nop dword ptr ds:[rax+rax],eax         |
00007FF62C5F1080 | lea rcx,qword ptr ds:[7FF62C5F2220]    | 00007FF62C5F2220:"hello, world\n"
00007FF62C5F1087 | call <reversingsample.printf>          |
00007FF62C5F108C | sub rbx,1                              |
00007FF62C5F1090 | jne reversingsample.7FF62C5F1080       |
00007FF62C5F1092 | call qword ptr ds:[<&_fgetchar>]       | 
00007FF62C5F1098 | xor eax,eax                            | 
00007FF62C5F109A | add rsp,20                             | 
00007FF62C5F109E | pop rbx                                |
cs

 

  뭔가 첨에는 조금 달라 보였지만, 루프 코드를 보면 3번째 라인에서 ebx 레지스터에 5 값을 넣는다(mov ebx,5). 그리고 4, 5번 라인의 앞에서 이미 봐서 익숙한 패턴의 코드를 통해서 "hello world" 를 출력해 준다. 그리고 7번 라인을 보면 rbx(ebx 와 동일한 레지스트의 64비트 이름)에 1씩 빼준다(sub rbx,1). 이후 jne(jump not equal) 가 호출 되어 rbx 가 0인지 체크하여 0일 때까지 다시 5번째 라인인 7FF62C5F1080 주소로 점프하며 프린트를 반복한다.

 

  결국 실제 릴리즈 되어 실행되는 코드는 아까 디버깅에서 봤던 어셈블리와 모양은 다르지만 하는 일은 같은 등가의 코드라는 것을 알 수 있다.

 

 

 

 

3. 리버싱에 대해 생각해 보기

  앞의 예제를 통해서 짧게 격투기 선수의 초보 줄넘기 같은 짧은 리버싱 과정을 살펴 봤다. 그럼 다시 근본적인 문제로 돌아가서 왜 우리가 저렇게 귀찮은 과정을 거치면서 리버싱을 해야할까? 가장 큰 이유는 프로그램 소스가 없기 때문이다. 소스가 있는 프로그램을 굳이 리버싱을 하는 것는 KTX 열차 표가 있는데 뛰어서 서울에서 부산까지 가려하는 행동하고 비슷할 것이다.

 

  보통 리버싱 작업의 유용성을 예로 드는 데 사용하는 악성 코드는 소스를 모르는 채로 시스템을 공격하는 대상이고, 해당 실행 코드의 동작을 예측하고 방지하기 위해서는 내부 동작을 알아야 하기 때문에 궁극적으로는 리버싱 밖에는 방법이 없다(물론 여러가지 정적인이거나 해시 값을 체크하는 등의 히스토리에 기반한 방법도 있겠지만 그 쪽은 운이 좋은 편이라고 봐도 될듯 하다). 물론 악성 코드 분석과 같이 일정한 가상환경에 넣어놓고 동작을 관찰하는 보조적인 방법도 충분히 의미는 있을 것이다.

 

 

  리버싱 책을 보면 보면 보통 간단하게 어셈블리 코드의 동작 원리에 대해 설명한 후, 코드에서 원하는 위치를 찾아 로직을 이해하거나, 값이나 기능을 원하는 대로 패치하는 부분을 설명하고, 이후 PE파일의 구조, DLL 인젝션이나, API 후킹, 리버싱을 방해하는 방어수단에 대처하는 방법들을 설명하곤 한다.  그런데 사실 그러한 측면에서의 리버싱은 합법과 불법을 넘나들기도 한다. 같은 기술을 이용하여 타사의 프로그램의 동작 원리를 이해하여, 카피하고자 하는 용도로도 마찬가지로 사용될 수 있기 때문이다. 또한 긍정적인 측면으로는 많은 백신이나, 보안 툴들이 시스템 사용자의 허가(설치와 동의)를 기반으로 시스템을 보호하는데도 사용되며, 반대로 악성코드들은 그런 기술을 이용하여 사용자가 인지 못하는 사이에 동의를 얻어(택배문자를 클릭해 가짜 택배 프로그램을 설치한다던가) 자신을 숨기거나 사용자가 모르는 사이에 사용자의 자료나 행동을 기반으로 나쁜 행동을 벌이곤 한다.

 

  나아가 파이썬 시간에도 얘기했지만, 프로그램은 혼자서 움직이지 못하는 존재이다. 사용자가 직접 만든 코드 로직은 정말 적은 핵심 부분에 불과하고, 나머지는 타인이 만들어 놓은 모듈을 사용하고 있고, 해당 모듈도 해당 모듈의 언어에서 제공하는 라이브러리나, OS에서 제공하는 API(이것도 마찬가지로 코드라고 볼수 있다)를 사용해 해당 행동을 하게된다. 이 먹이사슬과도 같은 관점에서 보게 되면 여러분이 어셈블리를 잘 이해하고 미지의 프로그램을 잘 이해하려 한다면 해당 프로그램을 구성하는 언어 및 OS 의 API, OS의 동작 원리에 대해 잘 이해해야 한다는 결론이 나오게 된다. "Windows Internal" 같은 OS 동작을 설명하는 책부터, 시스템 프로그래밍 책 들과 같은 부분이 그런 시도의 출발점이 아닐까 생각한다. 좀 더 낮은 레벨에서 시스템을 안전하게 하려고 하면 할 수록, 이러한 평소에는 상위 언어에 감싸여 신경쓰지 못했던 부분들에 익숙해 져야 하는게 어쩔수 없는 면 같다.

 

  추가로 많은 악성 코드의 같은 경우는 외부와의 통신을 통해서 주요 로직 모듈을 다운 받거나 데이터를 받거나 전달하는 일도 많기 때문에, "통신" 이라는 개념에서 웹이나 다른 소켓 프로토콜과도 연관이 되어 버리게 되는 부분 같다. 그러다 보면 데이터 은닉(data hiding) 이라는 정상적인 것으로 위장해 하는 행동을 숨기려고 하는 쪽과도 주제가 만나게 된다.

 

 

 

 

4. 다른 측면의 리버싱

  위에는 코드에 기반한 얘기만 했지만, 블랙박스 테스트를 잘하면 화이트박스 기법과는 다른 측면에서 적절하게 주요 코드의 동작을 예측할 수 있고, 자바스크립트를 잘 분석하면 서버 사이드의 로직을 어느 정도 예측할 수 있듯이, 외부에서 관찰을 하여 분석하는 리버싱 영역도 있을것 같다.

 

  종종 만나는 부분이 게임 커뮤니티 같은데에서 사용자가 여러 실험을 통해서, 데미지 규칙을 유추 한다는지, 하는 부분이다. 사실 이런 부분은 서버 사이드 로직이기 때문에, 클라이언트를 아무리 분석해 봤자, 코드가 없을 테니 알 수가 없다.

["랩이 깡패다" 분석 실험 - 인벤]

http://www.inven.co.kr/board/lineagem/5056/4500

 

  뭔가 잘 짜여진 시나리오로 이런 반복적인 테스트를 하는 부분은 충분히 다른 측면의 리버싱이기도 하고, 클라이언트의 기반의 리버싱의 한계를 넘을 수 있게 하는 부분이기도 하다. 마치 별의 움직임을 관찰해서 행성들의 움직임의 규칙에 대한 가설을 세우는 일과 비슷하다고 보면 너무 거창한가는 싶지만 말이다.

 

 

 

 

5. 포렌식 예제 만들어보기

  뭐 예제라고 그러긴 좀 민망하긴 하지만, 포렌식의 측면을 살짝 엿볼수 있는 2개의 예제를 파이썬 코드로 만들어 살펴 보려고 한다.

 

 

5.1 특정 파일에 대한 생성, 수정, 접근 시간 보기

   첫번째는 파일의 생성일과 수정일 히스토리를 살펴보려고 한다. 우선 메모장을 열어서 "test" 라고 적고 c:\python\code 폴더에 test.txt 라고 저장한다(create). 이후 다시 해당 파일을 열어 "modify" 라고 다음 줄에 적고 다시 저장을 한다(modify). 이후 다시 해당 파일을 열어서 본다(access). 우리는 파일을 수정해서 저장하면 해당 파일만 남는 다고 생각하지만, NTFS 시스템은 해당 파일에 대해 생성, 수정, 접근에 대한 기록을 저장해 놓는다.

 

 

 

  "test.txt" 파일에 대한 해당 속성을 보기 위해 아래와 같은 파이썬 코드를 생성해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os, time, datetime
import stat
 
file = "test.txt"
 
# 파일 생성일 출력
created = os.path.getctime(file)
created_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(created))
print("생성 시간: " + created_time)
 
 
# 파일 수정일 출력
modified = os.path.getmtime(file)
modified_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(modified))
print("수정 시간: " + modified_time)
 
# 파일 접근일 출력
accessed = os.path.getatime(file)
accessed_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(accessed))
print("접근 시간: " + accessed_time)
cs

 

  내용을 보면 os모듈을 이용해서, 각 속성을 얻어와서 화면에 뿌린다. c:\python\code 폴더에 "UTF-8" 포맷으로 "show_timestamp.py" 라고 저장한다(잘 모르겠으면 파이썬 2교시를 참조). 이후 해당 파일을 실행 한다.

 

C:\Python\code>python show_timestamp.py
생성 시간: 2019-09-01 13:23:50
수정 시간: 2019-09-01 13:24:00
접근 시간: 2019-09-01 13:24:00

 

  근데 내용을 보면 이상하게 맨 마지막으로 파일을 열어본 시간이 안 찍히고 있다. 구글을 찾아보면 윈도우즈 7부터 성능을 위해서 파일이나, 디렉토리가 접근되었을때는 해당 정보를 업데이트 안한다고 한다. 이를 수정하기 위해서는 NtfsDisableLastAccessUpdate 레지스트리 키를 0으로 하면 된다고 한다. 뭐 해보고 싶으면 해봐도 좋을듯 싶다.

 

[Enable last access time - Open Tech Guide 사이트]

https://www.opentechguides.com/how-to/article/windows-10/129/enable-last-access-time.html

 

 

 

5.2 엑셀의 최근 항목을 레지스트리에서 찾아보기

   두 번째는 엑셀의 최근 접근 문서를 찾아보자. 엑셀을 열어보게 되면, 아래와 같이 최근 열었던 문서가 표시된다(오피스 16버전 기준).

 

 

  구글을 찾아보면, "HKEY_CURRENT_USER\Software\Microsoft\\Office\16.0\Excel\\File MRU" 경로에 있다고 나온다. 여러 전문적인 포렌식 툴도 있겠지만, 실행 창에서 "regedit" 를 입력하여 레지스트리 편집기를 열어 해당 경로로 이동해 보면, 아래와 같이 엑셀 창에서 봤던 최근 문서들이 키/값 으로 저장되어 있다.

 

  그대로 끝내긴 좀 그래서, 파이썬으로 해당 레지스트리 키를 가져와서, 최근 문서 값 만을 뿌려주는 코드를 하나 작성했다.

1
2
3
4
5
6
7
8
9
10
11
import winreg
# 키를 정의 한다.
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\16.0\\Excel\\File MRU")
 
# 키 안에 담김 value 숫자를 얻어 그 숫자 만큼 루프를 돈다.
for i in range(0, winreg.QueryInfoKey(hKey)[1]):
    name, value, type = winreg.EnumValue(hKey, i)
 
    # Item 이라는 문자열로 시작하는 이름일 때 프린트 한다.
    if name.startswith("Item"):
        print (name + ": " + value)
cs

 

  역시 c:\python\code 폴더에 "UTF-8" 포맷으로 "show_excel_recently.py" 라고 저장 후 실행해 보자. 아래와 같이 원하는 결과가 나온다.

 

C:\Python\code>python show_excel_recently.py
Item 1: [F00000000][T01D415FA193618D0][O00000000]*C:\Python\code\07교시\result.xlsx
Item 2: [F00000000][T01D37D59BFAC7C70][O00000000]*C:\Backup\code\result2.xlsx
Item 3: [F00000000][T01D37D59BBFEB6B0][O00000000]*C:\Backup\code\result.xlsx

 

 

 

 

6. 포렌식에 대해 생각해 보기

  예제를 보았으니, 앞의 리버싱 얘기에 연결해서 전혀 다른 분야 같이 보이는 포렌식(forensic)에 대해 생각을 해보자

 

  포렌식의 가장 기본이 되는 형태는 삭제된 파일을 복구하는 undelete 작업일 것이다. 속도의 문제 때문에 컴퓨터에서 파일을 삭제할때(휴지통에 남기지 않더라도), 시스템에 등록된 파일에 대한 정보 위주로 삭제하고, 실제 파일 내용이 저장된 디스크의 자기 공간 부분은 그대로 남겨두게 된다. 

 

  왜 전체 내용을 다 지우지 않냐고 생각할 수도 있겠지만, 파일이라는 것은 디스크 내에 0과 1로 저장된 정보의 형태고, 그것을 완전하게 지우기 위해서는 랜덤한 형태로 (여러번) 덮어 씌워야 한다. 그렇다는 것은 몇 기가 정도의 파일을 완전히 삭제하려면 적어도 해당 파일을 디스크 사이에서 복제하는 시간만큼 동일하게 소요되게 된다는 것이다.

 

  매번 파일을 지울때마다(이건 사용자의 파일도 있을 수 있고, 시스템이 사용하는 파일일 수도 있다) 그렇게 번거로운 작업이 일어난다면 지금의 컴퓨터는 휠씬 더 많이 느려질 것이다(큰 파일 복사를 할때나 큰 용량의 파일을 여러개 다운로드 받을때 컴퓨터가 느려지는 현상을 생각함 될듯 하다). 그래서 파일을 지운 후 딱히 컴퓨터를 더 사용되지 않아 해당 파일이 저장되 있던 디스크 영역이 덮어써 지지만 않는다면, 원래의 파일로 복구가 가능할 가능성이 높게 된다. 물론 삭제를 해도 기존에 저장되었던 잔여 자기장에 기반하여 복구가 가능하다고는 하지만, 현실적으로는 가용 가능한 정보가 얼마나 복구될까는 싶다. 

 

[소거 프로그램 - 나무위키]

https://namu.wiki/w/%EC%86%8C%EA%B1%B0%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8

 

  앞의 어셈블리(또는 c++같은 낮은 수준의 프로그래밍 언어) 코드를 보면 알수 있지만, 컴퓨터는 의외로 생각보다 단순한 동작의 반복으로 우리가 보는 이 운영체체와 프로그램의 동작들을 구현한다. 단순하게 본다면 레지스트, 메모리, 디스크에 데이터를 썼다 지우거나 하는게 전부라고 봐도 될지 모른다. 그러한 과정에서 파일 삭제의 경우와 같이 무언가 완전히 덮어지지 않고 디스크 내에 남거나, 운영체제나 어플리케이션 들이 모니터링이나, 디버깅, 사후 추적의 목적으로 사용자 몰래 남기거나, 사용자가 부주의 해서 또는 필요해서 남긴 부분들이 존재하게 될 것이다. 또한 특수한 경우를 빼고는 메모리 상에는 대부분의 보호장치가 풀어지고 실행가능한 코드만 남게 되기 때문에 접근이 가능하다면 의미 있는 정보를 추출하는 것이 가능하다. 이런 부분들에 대해서 살펴보고, 복구하고, 시간의 측면에 따라 재구성 하여 사용자의 행동을 이해하고 증명하는 작업이 포렌식이란 영역이 아닐까 싶다.

 

  조금 공식적으로 얘기하자면, 그러한 데이터들을 이쪽에서는 아티팩트(artifact) 라고 부르는 것 같은데 그 아티팩트들이 존재할 수 있는 장소는 메모리 상의 여러 요소(실행되는 프로세스, 문서들), 평문으로 저장된 패스워드, 채팅 등 사용자간에 전달되는 데이터, 네트워크 커넥션 로그, MBR , 레지스트리, 로그 및 컨피그 파일, 프로그램 파일, 임시 파일, 데이터 파일, 삭제 후 유지되는 데이터 공간 등이 있을 것이다.

 

  앞에서 보안은 데이터의 흐름을 따라가는 일이라고 생각한다고 말했었는데, 모든 보안의 분야가 그렇겠지만 포렌식 분야야 말로 데이터에 포커스를 두고 흔적을 따라가는 일이라고 본다. 이상적으로는 데이터가 담긴 메모리, 디스크에 모든 필요한 정보가 담겨있다고 볼수 있지만, 해당 데이터들이 구체적인 사용 주체들과 연결되지 않는다면 0과 1로 만들어진 숫자에서서 쉽게 의미를 찾을 순 없을 것이다. 그렇게 하기 위해 OS 에 따른 디스크 및 메모리의 사용 방식, 프로그램이 동작하는 방식, 사용자가 생각하거나 움직이는 방식(사용자가 생성하는 데이터를 이해하거나, 사용자가 컴퓨터를 사용하는 용도나 스타일에 따라서도 접근이 달라질 수 있다고 본다)까지도 이해할 필요가 있다고 생각한다.

 

  나아가 명시적이진 않은 어플리케이션의 데이터를 추적하고 찾기 위해서는(역시 앞의 리버싱과 마찬가지로 소스를 모르니까) 앞 단에서 다르게 느껴졌던 리버싱 영역의 스킬들이 필요하게 될 가능성이 높아진다. 여기서에 두 개의 영역이 결국 만나게 된다고 생각하는데, 프로그램이나 운영체제의 행동을 쫓던 리버싱은 대상이 만들어내거나 접근하는 데이터에 관심을 가지게 되고, 데이터를 쫓아가던 포렌식은 데이터를 만들어 내는 프로그램이나 운영체제의 행동에 관심을 가지게 되어버린다. 결국 두 개의 분야의 지식이 만나 상호 작용하며 균형을 이루어야만 각 분야가 완전하게 될수 있는 것 같다.

 

 

 

 

5. 마무리 하면서 

  처음부터 많이 돌아오긴 했지만 리버싱과 포렌식은 서로 거리가 있는 분야가 아니며 서로의 영역의 지식이 필요한 연결되어 있는 분야라는 얘기를 하고 싶었다. 더 나아가면 보안의 다른 분야에서도 이러한 낮은 레벨의 지식이 필요한 분야가 전체적인 보안의 균형과 응용을 만들어 주는데 꼭 필요하다는 생각을 한다.

 

  그리고 현실적으로는 포렌식 같은 증명을 하는 작업은 객관적이고 표준적인 방법이 필요하므로(법적으로 증명해야 되는 경우도 점점 더 늘어나고 있을테니), 개인의 특별한 지식 보다는 공인된 기관에서 보안적으로 인증된 툴을 사용하여 증적을 만들어야 하는 경우도 많을 것 같다(회사가 인증이나 감사를 받을 때의 기준을 생각하면 이해가 될 것이다).

 

  다만 해당 툴의 결과를 올바르게 해석하거나 툴의 한계를 극복하는 부분은 앞의 스캐너 부분에서 얘기한 스캐너와 수동 테스트의 관계와 마찬가지라고 본다. 또 다른 측면에서는 취미나, 새로운 방법론을 연구하거나, 자동화 때처럼 공식적인 툴들이 지원하지 않는 마이너한 영역에 대해서 연구하는 측면도 있을 것 같다. 이렇게 보면 보안은 결국 컴퓨터라는 세상을 이해하는 일인 것 같기도 하다.

 

 

2019.9.1 by 자유로운설탕
cs

 

 

 

 

 

 

 

 

 

 

posted by 자유로운설탕
2019. 7. 7. 19:32 보안

  이번 시간에는 자동화에 대해서 얘기하려 한다. 보안은 많은 부분이 자동화를 기반으로 구축되어 있는 분야기도 한것 같긴 하지만, 그러한 부분을 조망하는 얘기를 하려는 것은 아니고, 한 개인의 입장에서 생각 할 수 있는 부분이 어떤가에 대해서 이야기 하려 한다. 



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 


 


1. 들어가면서

  자동화에 대해서는 이전의 파이썬 글에서도 많이 언급이 된 주제이다. 웹 자동화에 대해서는 8교시 정규표현식, 11교시 웹 페이지 파싱, 12교시 웹 자동화편을 걸쳐서 얘기를 했고, 13교시에서 윈도우즈 자동화를, 14교시에서 작업에 대한 자동화를 이야기 했다. 10교시의 Whois API 이용도 API 를 호출해 자동화 한다는 측면에서 마찬가지이다(마음의 여유가 있다면 이번 시간을 보기전에 해당 글들을 가볍게 보고 왔음 한다)

 

  파이썬 글에서는 그렇게 나눠 놓긴 했지만, 지금와서 드는 생각은 실제 자동화는 저 부분들이 서로 섞여서 진행되는 것이라는 것이다. 조금 더 나아가면 우리가 프로그래밍 한 줄을 실행하는 시점 부터 자동화를 수행하고 있다고 봐도 될 듯 하다. 지금 이 글을 쓰고있는  컴퓨터 자체나, OS, 브라우저, 엑셀 포토샵, 일러스트레이트 같은 디자인, 편집 프로그램, 매일 재미있게 하고 있는 게임 등도 결국은 자동화의 결정체라고 보는게 맞지 않을까 싶다. 사람은 항상 효율성을 추구하는 방식으로 생각하기 때문에(어쩌면 사회적 훈련의 결과일지는 모르겠지만), 사람이 하는 많은 일들은 점점 성숙이 되다보면 자동화로 귀착되어 버리는 것 같다.

 

  보안쪽에서 흔히 솔루션이라고 얘기하는 많은 하드웨어(보통 소프트웨어가 기반이긴 하지만)와 소프트웨어도 결국은 보안 업무의 자동화가 구현된 결과라고 볼수 있다. 우리가 당연한게 사용하고 있는 백신이나, 각종 제어 솔루션, 모니터링 시스템, 방화벽, IPS, 여러 회사들의 보안 솔루션들도 결국은 보안적 지식을 자동화 하여(결과적인 판단 부분에서는 아직 사람들이 많이 관여해야 하겠지만) 구현한 결과라고 볼수 있다. 이 부분에 대해서는 파이썬 21교시의 "The five orders of ignorance" 에서 소개한 지식을 담는 매체의 관점으로 이해하면 어떨까 싶다.

 

 

 

 

 

 

 

2. 보안에서의 자동화 

  맨 처음 부분에서 "한 개인의 입장에서"라고 범위를 좁혀놓은 이유는 이젠 보안 쪽에 있는 많은 자동화의 부분이 개인이 감당하기에는 범위 및 정보가 너무 커졌다고 생각하기 때문이다. 백신 같은 분야만 봐도 예전에는 한명의 개인이 만들어 유지보수가 가능한 부분이였다면, 현재는 수많은 사람들이 해당 회사에 속해서 24시간 돌아가면서 새로운 악성코드 들에 대응을 해야되는 분야가 된것 같다. 또한 백신이 최신의 악성코드 공격을 따라기기 위해서 수집 해야하는 다양한 데이터의 양도 이제 한 개인이 수집하기 에는 너무 다양한 환경과 분석해야 될 데이터의 양이 존재한다(데이터를 준다해도 저장한 서버를 살 돈이 없어서라도 못할듯 싶다--;). 백신 회사들이 개인에게는 대부분 무료 백신을 제공하는 부분도 점유율을 높인다거나, 위험한 PC 들이 너무 늘어나게 되는 것을 기본적으로 방지하는 목적도 있겠지만, 다양한 사용자들의 환경과 그곳에서 발견되는 악성코드를 수집하기 위한 목적도 큰 부분을 차지할 것 같다. 그렇게 보면 세상에 공짜는 없어 보인다. 

 

  이러한 부분은 네트워크, 스캐너, 디비 접근 제어 및 모니터링, 매체제어 솔루션, 패치 등의 관리도 그렇고, 포렌식이나 리버싱 툴도 마찬가지인것 같다. 이젠 작은 개인이 이러한 부분에 대해서 무언가 의미있는 성과를 내기에는 관련된 환경과 데이터가 감당할 수준을 넘어버렸고 ROI와 계속적인 유지보수 측면에서 어려운 시기가 된것 같다. 또한 만들어 놓은 부분에 대해서 잘 동작하느냐를 증명하는 QA 부분도 더더욱 해당 부분을 어렵게 만든다. 또한 필요한 기술의 광범위 함도 한 몫 한다.

 

  예로서 만약 디비 접근 모니터링 솔루션을 만들고 싶다면 여러 데이터베이스의 통신 패킷들을 파싱하고 원하는 값들을 뽑을 수 있어야 한다. 물론 현재에 딱 맞는 오픈 라이브러리가 있을지는 모르지만 그 라이브러리가 적용 후, 새로운 데이터베이스 버전이 나왔을 때 더이상 업데이트가 안된다면 어떻게 하겠는가?(다른 많이 사용되는 오픈소스 라이브러리들도 마찬가지라고 생각될지 모르지만, 수많은 사람에게 필요한 데이터베이스에 쿼리를 날리는 범용의 목적을 가진 라이브러리와, 데이터베이스 패킷을 파싱하는 특수한 라이브러리는 유지보수가 계속 될 확률이 많이 차이가 난다고 본다). 물론 커다란 인력 풀을 가진 회사들에게는 직접 개발 및 유지보수에 대한 제약이 적을지는 모르겠지만, 일반적으로는 성숙된 분야의 툴을 스스로 만드는 것은 힘든 부분 같다.

 

 

 

  그렇다면 작은 개인은 무엇을 자동화 해야할까? 이 부분은 틈새 시장이라는 표현으로 생각함 어떨까 싶다. 아직 변동성이 크거나 시장성이 없어서 정식의 외부 솔루션들이 개발되지 않거나, 솔루션이 있더라도 많은 투자를 하지 않아서 어설픈 분야들이 있다. 또는 솔루션을 들여놓기에는 너무 부담이 되서 부족하게 나마 유사한 효과를 가진 프로그램을 만들고 싶을 때도 있을 것이다. 또는 솔루션의 기능이 부족해서, 뭔가 추가적인 보충을 해보고 싶을 때, 솔루션 개발사나 오픈 소스에서는 보통 해당 부분의 개선이 여러 이유로 쉽지가 않다(수많은 요청을 하나하나 들어주다가는 수 백개의 유지해야할 소스 트리가 생길 수도 있다). 아님 최악의 경우 예산이 없어서 몸으로 때울 수는 없고 비슷하게 라도 만들어 땜빵을 해야 할지도 모르겠다.

 

 

  또 다른 측면에서는 일상의 모든일이 해당된다고 봐도 괜찮을 듯 싶다. 엑셀 작업을 돕는 수많은 매크로 및 함수들이 있듯이, 파이썬 같은 간편한 언어를 가지고 일상의 작은 일들을 자동화 하는 것도 괜찮아 보인다.  처음부터 거창한 무언가를 만들려 하는 것보다는 이러한 작은 조각 조각들을 구현 하다보면, 언제가 꽤 큰 퍼즐에 대해서도 그 동안 모은 조각들을 기반으로 처음 생각보다 어렵지 않게 만들어 낼 수도 있다. 개인적으로 생각하기엔 이런 접근이 프로그램을 주 직업이 아닌 보조 기술로 접근하는 사람들에게는 현실적일 듯도 싶다.

 

  예를 들어 데이터베이스의 모든 테이블에 대해서 특정 쿼리를 날려서 원하는 정보를 가져오는 일을 해야 하는 경우, 쿼리 분석기에서 일일히 쿼리를 만들어낼 수도 있겠지만(SQL 이나 텍스트 편집기들을 잘 쓰는 사람들은 여러 팁을 통해서 모든 테이블에 대한 쿼리를 일괄로 만들어 낼수도 있긴 하겠지만), 파이썬으로 데이터베이스에서 테이블 목록을 얻어낸 후, 적당히 조건에 맞는 문법과 조합하여 쿼리를 만든 후, 각각의 쿼리로 조회하여 결과를 엑셀 등에 정리하면 좀더 편할 것이다. 추가로 해당 작업이 매일매일 일어나거나, 쿼리가 자주 특정 패턴으로 변경 되거나, 대량으로 일어난다면 시간을 아끼게 된다고 느껴 매우 만족하게 될 수도 있다.

 

 

  여러 오픈 소스로 제공되는 대시보드 같은 프로그램 들도 결국은 이러한 자동화에 대한 장벽을 해당 분야에 익숙한 전문가들이 낮춰주려 하는 노력인것 같다. 그렇게 보면 Redis 나 Elastic Search 같은 데이터베이스도 키, 값을 쉽고, 효율적으로 저장하게 해주는 많은 노력들이 자동화의 산물로 만들어져 제공 된다고 보면 된다. 파이썬 같은 프로그래밍 언어나 거기에 속해져 전세계 사람들에 의해 무료로 개발되어 제공되고 있는 모듈들도 마찬가지 인것 같다. 우리 중 일부는 가끔 자동화의 1차 생산자가 되기도 하지만, 대부분의 사람들은 2차 생산자나 3차 생산자인 경우가 많은 것 같다.

 

  이렇게 보면 특정 분야의 자동화라는 말은 별 의미는 없는것 같기도 하다. 적용되는 형태는 다를지 몰라도, 적용되는 기술의 배경은 많은 부분들이 서로 겹치게 되는 것 같다. 결국은 자동화는 프로그래밍 또는 프로그래밍으로 감싸진 좀더 사용자 친화적인 2차 기술 들을 이용하여 일을 하는 방식 같다.

 

 

 

 

3. 자동화 예제 구현해 보기 - 데이터베이스에서 개인정보 찾기

  보안 쪽에서 가장 많이 쓰인다고 생각되는 자동화 프로그래밍 요소중 하나는 패턴에 대한 해석인것 같다. 그 중에서도 으뜸은 파이썬 8교시에 얘기한 정규 표현식 이라고 본다. 나중에 얘기할 모니터링과도 연결될 내용이긴 하지만, 결국 데이터를 기반으로 모든걸 자동으로 검사할 수 밖에 없기 때문에, 결국 통계든 Raw 데이터 자체든 패턴을 찾아야 한다. 물론 특정한 데이터베이스 안의 숫자의 변화를 체크하는 경우도 있긴 하겠지만, 그 숫자가 만들어지는 배경을 살펴 보게 되면 특정한 조건이 발생할때 쌓이는 경우라고 볼 수 있고, 그런 특정한 조건이 주어지는 상황 또한 패턴이라고 볼수 있다고 본다.

 

  여하튼 현재 보여줄 예제는 데이터베이스 안의 모든 테이블에 대해서 10건씩 데이터를 조회해서, 모든 컬럼에서 개인정보를 검사한 후, 결과를 반환하는 프로그램이다.

 

1) 데이터베이스내의 테이블을 가져오는 쿼리는 구글에서 "mssql get tables" 로 조회해 얻었다.

 

2) 기본적으로 테이블에 조회 쿼리를 만들고 행을 가져오는 부분은 파이썬 7교시의 내용을 pyodbc 모듈을 사용하는 것으로 변환해 적절히 만들었다(책에는 pyodbc 로 진행되게 됬어서 해당 샘플을 가져왔다). 예전 댓글이 생각나서 가능한 인자가 호출되는 부분은 parameterized query 로 만들었긴 했는데, 사실 어플리케이션 관점에서 보면 작업의 시작인 테이블 리스트를 가져오는 함수에 인자 자체가 없고, 테이블 이름이나, 컬럼 이름은 데이터베이스 내부에서 가져온 값이기 때문에 조작 가능성은 거의 없다고 봐도 될듯하다.

 

3) 개인정보 패턴을 찾는 정규 표현식은 구글에서 찾다가 행정 자치부에서 배포한 "홈페이지 개인정보 노출 가이드라인"의 83페이지를 참조했다(개인적으로 언어별로 최신 패턴으로 유지보수 되는 라이브러리를 제공하면 더 좋을 것 같다).

https://www.privacy.go.kr/nns/ntc/selectBoardArticle.do?nttId=5952

 

4) 마지막으로 어떻게 테이블, 컬럼, 10개 행을 어떤 자료구조에 넣어서 적절히 루프를 돌리면서 개인정보를 찾은 후 결과를 정리할 수 있을까 생각하다보니, Pandas의 Dataframe 을 사용하면 어떨까 싶었다. 개인적으로 dataframe 은 메모리에 떠있는 디비라고 생각하며 사용하고 있는데, 파이썬에서 간단하게 세로 열을 순서대로 가져와 검사할 수 있기 때문이다(예: A 테이블의 10개 행 중에 메모 컬럼에 해당하는 10개 데이터). 왠지 RDB 에서 가져온 결과를 담은 결과 리스트를 인덱스를 통해 조작하는 것보다는, openpyxl 같은 엑셀 형태의 객체나 pandas 같은 데에 넣는 것이 좀 더 편하게 원하는 값을 선택을 할 수 있을 것 같았다.

 

 

  위에 얘기한 것을 그림으로 정리하면 아래와 같다. 

 

 

  코드를 보기전에 개인정보가 담긴 테이블이 있어야 결과가 나올 것이기 때문에, 우선 MSSQL Management Studio 를 실행하여 기존 데이터베이스에 테이블을 2개 추가하고 데이터를 넣자)쿼리를 실행할 줄 모른다면 3교시를 다시 참고한다)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
create table Secret_A
(
    MemberNo int,
    MobileTel varchar(20),
)
 
go
 
insert into Secret_A
values (10'010-1111-2222')
 
insert into Secret_A
values (20'010-3333-4444')
 
go
 
create table Secret_B
(
    MemoNo int,
    Memo varchar(100),
)
 
go
 
insert into Secret_B
values (2000'제 전화번호는 011-1234-5678입니다.')
 
insert into Secret_B
values (2001'이건 개인정보가 아니예요 010-2222')
cs

 

 

  테이블을 생성하고 조회하면 아래와 같이 조회 데이터가 나온다. 내용을 보게 되면 Secret_A 테이블에는 2개의 행에 모두 핸드폰 번호가 있고, Secret_B 테이블에는 1개의 핸드폰 번호가 있다. 파이썬과 보안 글을 모두 실습했다면 이 두개의 테이블을 포함해 7개의 잡다한 테이블이 있을 것이다. 

 

  그럼 파이썬으로 만든 코드를 봐보자. 위에 그린 그림과 비슷하게 get_table_names 함수에서 테이블 이름들을 가져오고, get_column_names, make_column_query 함수를 이용해 make_dataframe 함수가 데이터베이스에서 조회한 내용을 dataframe 에 담는다. 이후 check_personal_pattern 를 이용해 각 컬럼별 데이터들에 대해서 루프를 돌리면서 개인정보를 찾아 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import pyodbc
import pandas as pd
import re
 
 
# 연결 문자열을 세팅
server = 'localhost'
database = 'mytest'
username = 'pyuser'
password = 'test1234'
 
# 데이터베이스에 연결
conn = pyodbc.connect('DRIVER={ODBC Driver 13 for SQL Server};SERVER='+server+' \
;PORT=1433;DATABASE='+database+';UID='+username+';PWD='+ password)
 
# 커서를 만든다.
cursor = conn.cursor()
 
 
# 테이블 이름을 얻어옴
def get_table_names():
    table_names = []
    
    sql_get_tables = "SELECT name FROM sysobjects WHERE xtype='U'"
    cursor.execute(sql_get_tables)        
    rows = cursor.fetchall()
    
    for row in rows:
        table_names.append(row[0])
 
    return table_names   
    
 
# 테이블의 컬럼이름을 얻어옴
def get_column_names(table_name):
    column_names = []
    
    sql_get_columns = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?"
    cursor.execute(sql_get_columns, table_name)        
    rows = cursor.fetchall()
    
    for row in rows:
        column_names.append(row[0])
 
    return column_names
 
 
# select 컬럼 쿼리 제작(a, b, c 이런 식으로 만들어진다)
def make_column_query(column_names):
    column_query = ''
 
    for name in column_names:
        column_query = column_query + name + ','
    column_query = column_query[:-1]
    return column_query
 
# 테이블의 내용을 읽어서 Panda Dataframe 에 저장한다.
def make_dataframe(table):
    columns = get_column_names(table)
    column_query = make_column_query(columns)
    
    query1 = "SELECT top 10 " + column_query + " FROM " + table + "(nolock)"
    df = pd.read_sql(query1, conn)
 
    return df
 
# 핸드폰 패턴을 찾아서 반환한다
def check_personal_pattern(table, column, check_items):
    # 결과를 반환할 배열
    detected_list = []
    # 핸드폰 정규 표현식
    mobile_tel_pattern = "(01[016789][-~.\s]?[0-9]{3,4}[-~.\s]?[0-9]{4})"
 
    for check_item in check_items:
        check_item = str(check_item)
        match_mobile_tel = re.search(mobile_tel_pattern, check_item)
 
        if  match_mobile_tel:
            matched_word = match_mobile_tel.group(1)
            matched_type = "핸드폰번호"    
            
            # 이미 다른 row가 걸린게 있다면 발견한 정보를 추가해 업데이트 하자 
            if detected_list:
                detected_list[0][3= detected_list[0][3+ " / " + matched_word
            # 처음 걸린 거라면 그냥 넣자
            else:
                detected_list.append([table, column, matched_type, matched_word])
             
    return detected_list
 
 
################
# 메인코드 시작
################
 
# 데이터베이스에서 테이블 이름을 얻어온다
tables = get_table_names() 
 
# 테이블 하나하나에 대해서, 10개의 row 를 불러와 dataframe에 넣은 후,
# 개인정보 검사를 하여 결과를 반환 한다.
for table in tables:
    print("[" + table + "]"
    
    my_dataframe = make_dataframe(table)
    
    # 컬럼 이름을 가져와서
    for column in my_dataframe.columns: 
        # 컬럼 이름에 해당하는 수직 row 데이터를 가져온다.
        lists = my_dataframe[column].tolist()
        
        # 개인정보가 있는지 검사한다
        for item in lists:
            personal_infos = check_personal_pattern(table, column, lists)
        
        # 찾은 개인정보를 출력한다
        for personal_info in personal_infos:
            print(personal_info)
cs

 

 

  c:\python\code 폴더에 인코딩은 UTF-8 로 해서 dbsearch.py 란 이름으로 저장 후 실행해 본다.

c:\Python\code>python dbsearch.py
[supermarket]
[order_record]
[escape_test]
[play]
[sms_cert]
[Secret_A]
['Secret_A', 'MobileTel', '핸드폰번호', '010-1111-2222 / 010-3333-4444']
[Secret_B]
['Secret_B', 'Memo', '핸드폰번호', '011-1234-5678']

 

  결과를 보면 개인정보 패턴이 없는 위의 supermarket 같은 테이블들은 그냥 넘어갔고, 개인정보가 들어있는 Secret_A, B 테이블에서만 개인정보를 찾아서 결과를 보여준다.

 

 

 

 

4. POC 이상의 것

  위의 코드는 어찌보면 POC(Proof of Concept) 레벨이라고 볼수 있다. 실제 환경에서는 몇가지 추가적인 확장점들이 필요하다.

 

  첫 째, 위에는 핸드폰 번호 하나밖에 없지만, 여러 개인정보 패턴을 넣도록 해야한다. 가이드에서 제시된 패턴이 실제 필드의 환경에 맞는 적절한 패턴인지도 검증도 해야된다. 특정 필드에서 쓰는 개인정보 패턴은 가이드에도 없을 수 있으므로, 정규 표현식으로 찾을 수 있는지 고민하고 구현을 해야할지도 모른다. 다른 개인정보 패턴을 넣게 되면 핸드폰 패턴을 검사하는 코드가 아마 더 복잡하게 되어버리게 될것 이다. 추가로 파이썬에서 쓰는 정규표현식 표현으로 일부 조정해야할지도 모른다.

 

  둘 째, 범위의 확장도 해야된다. 지금은 데이터베이스 하나밖에 없다고 가정하지만, 수많은 서버가 있고 해당 서버내에 데이터베이스가 복수개 있다면, 서버 목록을 관리하고, 해당 서버에서 데이터베이스를 찾는 부분이 확장되서 만들어져야 한다.

 

  셋 째, 아마 실제 필드에서 위의 프로그램을 돌리게 되면 수많은 false postive 들이 나올 것이다. 해당 부분이 패턴으로 찾는 방식의 맹점중 하나인데 그러한 부분들을 감소시켜 최종적으로 봐야할 데이터를 줄이는 여러 노력들이 필요하다. 안전한 컬럼명의 제외라든지, 특정 패턴의 예외라든지, 패턴들의 통계적 분포 라든지 등등, 패턴이 찾았지만 실제 개인정보가 아닌 데이터들을 자동으로 예외처리하는 여러가지 로직이 있어야 해당 툴을 사용하는 사람이 현실적으로 스트레스를 안 받고 유용하게 쓸수 있게 된다.

 

  넷 째, 결과를 어떻게 관리하고 보여줄지도 고민해 봐야 된다. 엑셀로 담을지 데이터 베이스로 담아 웹 페이지에서 관리하게 할지 등등, 히스토리나 결과에 대한 관리 및 검증에 대해서도 생각해 보면 좋을 것이다.

 

  다섯 째, 오라클, MySQL 같은 RDB 라면 위의 MSSQL 에 대한 코드를 비슷하게 수정해 쓸 수 있겠지만, Redis, MongoDB 같은 NoSQL 데이터베이스라면 앞의 데이터를 가져와 개인정보 검출 함수에 던지는 부분에 대한 정리를 해당 데이터베이스의 형태에 맞게 고민해 봐야한다. 쿼리 형태도 다를 뿐아니라, 넘어오는 데이터도 Json 베이스의 키, 값 데이터가 여러 깊이로 쌓여있는 경우가 많기 때문에 어떻게 파싱을 해야할지도 고민해야 한다. 또한 위의 테이블, 컬럼, 값에 대한 개념에 NoSQL 데이터베이스의 요소들을 어떻게 매칭 시킬지, 아니면 분리해 다룰지를 고민해야 한다. 인코딩, 디코딩에 관련된 데이터 깨짐 문제도 발생해 해결해야 할 수 있다.

 

  여섯 째, 검사해야 되는 대상자체를 유사도에 따라서 제외해야 할수도 있다. Elestic Search 의 날짜 별로 쌓이는 인덱스 라든지, Redis 의 캐시 성격의 키 같은 경우는 모든 것을 검사안하고 샘플링 하여 검사하는게 현실적일 수도 있다.

 

  일곱 째, 만들어진 프로그램에 대해서 어떻게 동작을 보장할 수 있을지에 대해서 검증 계획도 세워야 한다. 버그가 있을지도 모르는 프로그램이라면 안심하고 있다 뒷통수를 맞게 되거나, 프로그램의 효과를 자신하기 힘들게 된다.

 

  이렇게 열거하고 보면 할일이 엄청 많이 보이지만, 기본이 되는 POC코드가 만들어 졌으므로 하나하나 살을 붙여 보는 것도 꽤 재밌는 과정이 되리라 생각한다. 파이썬 글에서 얘기했던 여러 잡다한 주제들이 이런 과정에서 다들 현실화가 되서 하나의 툴로 동작하게 되는 것을 볼 수 있을지도 모른다.

 

 

 

 

5. 마무리 하면서

  자동화라는 망망한 바다 같은 주제를 짧은 예제하나로 요약해 소개해 좀 그렇긴 하지만, 앞에서 얘기했듯이 프로그래밍 자체가 자동화 이고, 이전 파이썬 글들도 결국은 자동화에 대해서 얘기한 것이라고 봐도 될듯 하다. 무언가 대단한 결과를 꿈꾸기 보다는 자신 주변에 있는 작은 자동화 요소들을 찾아서 조금씩 연습을 하면서 자동화의 씨앗을 키워보는게 어떨까 싶다. 

 

 

 

2019.7.13 by 자유로운설탕
cs

 

 

 

posted by 자유로운설탕
2019. 5. 20. 17:08 보안

  이번 시간에는 보안 쪽에서 백신 프로그램 만큼 자주 볼수 있는 스캐너(Scanner)에 대해서 이야기 하고, 사람이 직접 수행하는 수동 테스트(Penetration Test)에 비해 어떤 측면에서 장점이 있고, 어떤 측면에서 단점이 있는지를 살펴 보려 한다. 그렇게 함으로써 사람으로서 잘 할 수 있는 테스트가 무엇일지 생각해 보는 시간이 될 수도 있을 것 같다.



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 


 


1. 들어가면서

  스캐너는 무언가를 쭉 훝어 가면서 필요한 정보를 체크하는 이미지가 있다. 해당 정보는 문자 인식등에 필요한 글자 영역(광학 스캐너)일 수도 있고, 이상한 징후가 보이는 위치(공항 수하물 스캐너)일수도 있다. 보안 쪽에서 많이 쓰이는 스캐너들 또한 비슷한 역활을 한다. 웹페이지나 서버 등을 스캔하면서 이상하게 열린 포트는 없는지, 알려진 취약점 들이 없는지 체크를 하고 리포팅을 한다.

 

  이런 이상적인 관점으로 봤을 때엔 스캐너를 사용하면 모든 문제가 해결될 것처럼 생각되지만, 현실상으로는 많은 부분에서 제약이 있을 수 있다. 이번 장에서는 github에 공개된 간단한 스캐너 프로그램을 기준으로 스캐너 엔진의 기본적인 동작 원리를 이해하고, 여러 상용, 비상용 스캐너들을 어떤 관점에서 접근하면 좋은지에 대한 이야기를 하려한다. 

 

 

 

 

 

 

2. 공개된 스캐너 돌려보기

  처음엔 Acunetix 같은 상용 웹 스캐너의 트라이얼 버전으로 얘기를 풀어볼까도 했는데, 그렇게 되면 메인 주제를 벗어나 세팅 등 너무 설명할 부분이 많아질 것 같은 생각이 들었다. 또 아래 OWASP 의 정리된 페이즈를 보면 알겠지만, 윈도우즈 환경의 무료 스캐너 툴은 거의 없다고 봐도 될듯 싶다. 윈도우, 리눅스 양쪽을 지원한다고 했던 툴도 몇 개 다운로드 받아봤는데, 점점 리눅스 쪽만을 정식으로 지원하는 분위기로 가는 듯 싶다.

 

[Vulnerability Scanning Tools - OWASP]

https://www.owasp.org/index.php/Category:Vulnerability_Scanning_Tools

 

 

  그래서 "python 3 web scanner" 같은 키워드로 이리저리 찾다가 보니, 아래의 github 에 올려진 웹 스캐너가 괜찮아 보였다. 물론 상용 스캐너에 비해서는 좀 많이 어설프고(이 부분은 소스를 살펴보면서 얘기할까 싶다), 2017년 후에는 전혀 업데이트가 전혀 되지 않아 개선할 점이 많아보이는 소스이긴 하지만, 그 덕분에 소스가 복잡하지 않아 대략 전체적인 구조의 흐름을 따라갈 수 있을 것 같고 해서, 해당 스캐너를 다운받아 현재 진행 중인 파이썬 3.6.4 버전에 맞춰 고쳐서 실행해 보려고 한다.

 

[simple web scanner - lotusirous 님 github] 

https://github.com/lotusirous/simple-web-scanner

 

 

 

2.1 다운로드와 압축 풀기

  우선 해당 github 의 오른쪽에 있는 "Clone or download" 버튼을 클릭 한 후, "Download ZIP" 버튼을 클릭하여 zip 파일을 c:\Python\Code 폴더에 다운 받는다.  

 

 

   이후 바로 압축을 풀면 c:\Python\code 밑에 아래와 같은 폴더 구조가 될 것이다.

1
2
3
4
5
6
7
8
9
10
simple-web-scanner-master/
    xss_scanner.py
    crawler.py
    scanner_core.py
    scan_cli.py
    parser.py
    sqli_scanner.py
    vectors/
        sqli_vectors.txt
        xss_vectors.txt
cs

 

 

 

2.2 소스 조정 하기

  3.6.4 환경에서 돌려보니 에러가 나는 부분이 있어서, 소스를 조금 수정해야 한다.

 

 

  첫번째로 parser.py 이름이 파이썬 내부 모듈과 이름 충돌이 나서 HtmlParser 를 못찾는다고 나오는거 같으니, 파일 이름을 parser_x.py 라고 변경 한다.

 

 

  다음으로 해당 모듈을 불러오는 파일들도 같이 수정해야 한다. 우선 "xss_scanner.py" 와 "crawler.py" 파일을 열어, 처음에 모듈을 읽는 문장인 "from parser import HtmlParser" 문장을 "from parser_x import HtmlParser" 로 수정한다.

1
from parser_x import HtmlParser
cs

 

 

  이후 "scanner_core.py" 파일을 열어 import 문 아래에 "from urllib.error import URLError, HTTPError" 를 추가한다(돌릴 때 HTTPError 가 정의 안됬다는 에러가 났다).

1
2
3
from urllib.parse import urlparse, parse_qs, urlunparse, urlencode, unquote
import urllib.request
from urllib.error import URLError, HTTPError
cs

 

 

 

2.3 샘플 사이트 만들기

  이제 스캔 대상이 되는 사이트가 있어야 하는데, 3교시에 사용한 인젝션 페이지를 쓰면 될거 같은데, 이 스캐너가 몇가지 구조상 제약(개발이나 도메인 특성 반영이 덜 된 부분)이 있다.

 

  일단 처음에 HREF 태그를 기준으로 링크들을 크롤링 해서 가져오는데(상용 스캐너들은 이 스캐너와 달리 자바스크립트로 이루어진 동적인 링크 등도 모두 파싱해 가져온다), 테스트 할 페이지의 인자들을 리스트업 할때, Form 내의 값이나 헤더 값 등이 아닌, GET 으로 날라가는 인자(예: test.asp?name=tom) 를 기준으로 파싱한다. 그래서 "http://localhost/injection_test.asp" 식으로 링크 경로를 주게 되면, 페이지는 인지하지만, 따로 인자가 없다고 판단에 인자에 대한 스캐닝을 하지 않고 그냥 스캐닝이 종료되어 버린다.

 

  그래서 부득이하게 샘플 페이지를 스캐너에 맞추는 작업을 하려 한다. 3교시의 파일을 실습 했다는 가정하에(injection__test.asp 가 있다는 가정하에), c:\inetpub\wwwroot 에 ANSI 인코딩으로 "web_main.asp" 페이지를 하나 만든다. 해당 페이지는 크롤링시 injection_test.asp 페이지를 인지시키기 위해서 인자를 포함한 href 링크 하나만 달랑 가지고 있는 아래와 같은 페이지 이다.

1
<a href="http://localhost/injection_test.asp?txtBuyerID=tom">link</a>
cs

 

 

 

2.4 스캐너 소스 구조 설명

  일단 다른 사람이 만든 소스긴 하지만^^, 돌리기 전에 스캐너 구조에 대해서 이해는 하기위해 간단히 도표를 통해 설명하려 한다.

 

  전체적인 구조는 위의 그림과 같다고 보면 된다. 기능이 작은 쪽부터, 큰 쪽으로 보는게 날거라고 보는데, xss_vectors.txt, sqli_vectors.txt 파일을 보면 정말 간단하지만 XSS 취약점과, SQL Injection 취약점을 체크할 수 있는 몇 개의 인자 패턴들이 정의되어 있다(한번 열어서 내용을 보자). parser_x.py 는 페이지 소스에서 HREF 태그를 파싱하거나, <script> 태그 안의 내용을 가져오는 라이브러리로 크롤링이나 결과 판단시 사용된다.

 

  앞에 얘기한 2개의 txt 파일을 읽는 xss, sqli_scanner.py 파일에서는 txt 파일의 패턴을 가져다가 주어진 URL 뒤에 인자로 결합하는 작업을 하거나, 결과 값에서 공격이 맞다고 하는 패턴을 찾는 역활을 한다.

 

  Crawler.py 파일이 처음 명령어로 주어진 시작 URL 에서 유효한 링크들을 찾는 역활을 하며(확실친 않지만 depth 는 하나만 들어가는 걸로 보인다), 이후 scanner_core.py 파일에서 앞에 설명했던 기능들을 조합해 URL 에 공격 인자를 넣어 던진 후, 결과 페이지에서 해당 공격이 유효한지 소스 기반으로 체크하고 유효할 경우 결과를 화면에 뿌려준다.

 

 

 

2.5 스캐너 돌려보기

  그럼 스캐너를 함 돌려보자. 단순히 스캐너만 돌려서 결과를 보는 것은 큰 의미가 없고, 실제 어떤 요청들이 날라가는지를 함께 보려고 한다. 대량 건이라면 IIS 로그를 남겨서도 확인 할 수 있지만, 몇 건 안날아 갈거기 때문에 피들러를 켜서 보려 한다.

 

 

  우선 필요한 모듈을 설치하자. 설명서에 나온것에 추가해 html5lib 하나를 더 설치해야 한다.

c:\Python\code\simple-web-scanner-master>pip install bs4

c:\Python\code\simple-web-scanner-master>pip install html5lib

 

 

  피들러를 실행 후, 아래와 같이 xss 테스트를 하는 명령어를 넣어보자.

c:\Python\code\simple-web-scanner-master>python scan_cli.py --seedurl http://localhost/web_main.asp --engine xss
SELECTED xss engine
Crawl: 
http://localhost/web_main.asp
Crawl:  http://localhost/injection_test.asp?txtBuyerID=tom
http://localhost/injection_test.asp?txtBuyerID=tom HTTP Error 500: Internal Server Error
http://localhost/injection_test.asp?txtBuyerID=tom HTTP Error 500: Internal Server Error

 !! REPORT !!
http://localhost/injection_test.asp?txtBuyerID=<script>alert("XSS")</script>

 

 

  결과를 보면 web_main.asp 에서 injection_test.asp 페이지를 크롤링 해서 가져오고, 뒤의 인자를 기준으로 공격할 포인트를 찾는다(앞에도 얘기했지만 원래는 페이지 자체를 분석해서 폼이나 AJAX 방식의 공격 인자들을 하나하나 찾는게 정석이다). 이후 공격 패턴(보통 고급스럽게 페이로드-payload 라고 많이 불린다)들을 하나하나 조합해서 보내면서, 결과의 script 태그 안에 XSS 라는 공격이 성공했다는 시그니쳐가 있는지 찾는다(xss_scanner.py).

 

 

  피들러로 보게 되면 아래와 같다.

 

 

 

2.6 스캐너 구조에 대한 문제 생각해 보기 

  자 여기서 스캐너가 잘 돌아가는거 같다고 덜컥 쓰는건 아마추어가 할 행동이다(물론 상용 스캐너인 경우는 프로인 사람들이 만들었기 때문에 스캐너 자체를 의심하기 보다는, 뒤에 얘기할 옵션들과 필터들을 잘 조정하는게 맞다)

 

  여기서 한번 생각해 봐야할 문제는 만약 원래 페이지의 스크립트 태크안에 "XSS" 라는 문구가 있다면 오탐이 나올 수가 있다는 것이다. 보통 그래서 시그니쳐는 우연히 겹치기 힘든 기괴한 문자열 일수록 더 좋긴 하다. "False Positive(진짜라고 하지만 실제 아닌 것)"가 생기는 문제가 된다.

 

 

  한발 더 나아가, 위의 필터 중 나머지 하나인 sql injection 을 테스트하는  필터를 돌려서 다른 문제를 봐보자.

c:\Python\code\simple-web-scanner-master>python scan_cli.py --seedurl http://localhost/web_main.asp --engine sqli
SELECTED sqli engine

Crawl:  http://localhost/web_main.asp
Crawl:  http://localhost/injection_test.asp?txtBuyerID=tom
http://localhost/injection_test.asp?txtBuyerID=tom HTTP Error 500: Internal Server Error
http://localhost/injection_test.asp?txtBuyerID=tom HTTP Error 500: Internal Server Error

 !! REPORT !!

 

 

  문제가 없다고 나온다. 그런데 해당 페이지는 사실 예전에 SQL Injection 을 시연하기 위해 만들었던 페이지므로 SQL Injection 이 있을 수는 없다. 그럼 왜 일까? 실제 해당 페이지를 열어 홑따옴표(') 를 넣어보면 아래와 같은 에러 페이지가 나온다.

 

 

  해당 원인은 sqli_scanner.py 페이지를 보면 알 수 있다. 거기 정의된 SQL Injection 을 판단하는 시그니처는 아래 3가지 이다.

'SQL syntax', 'MySQL server', 'mysql_num_row',

 

  위의 에러 페이지에는 해당 문구가 포함되지 않기 때문에 안 나나 싶다면, 페이지에 나타난 "SQL Server 오류" 같은 시그니처를 하나 더 추가해 볼수도 있겠지만, 피들러로 호출된 내용을 보게 되면 근본적인 문제는 현재 페이지는 SQL Injection 이 나게 되면 500(Internal Server Error) 에러가 나게 되는데, 현재 해당 웹 스캐너의 코드(scanner_core.py 의 analyze 메서드)를 보면 에러가 나면 그냥 화면에 출력을 하고 멈춰 버린다(False Negative-괜찮다 하지만 실제 안괜찮은 것).

 

 

  이 경우는 아래와 같은 코드에서 에러가 난 경우에도 에러 상세 결과를 변수로 받아서 시그니처를 찾아보거나(해당 페이지에서 사용한 urllib 이 에러시에도 결과를 얻을 수 있는지는 고민해 봐야한다. 최악의 경우 사용하는 라이브러리를 다른 걸로 교체해야 될수도 있다), 그게 안된다면 차라리 500 에러가 났을 때 SQL Injection 이라고 판단하는게 좀더 합리적이지 않을까 싶다. 

1
2
3
4
5
6
7
8
9
    def analyze(self, url):
        # This function will get requests each vulnerable url to server to get response
        for mal_url in self.build_malicious_url(url):
 
            try:
                res = urllib.request.urlopen(mal_url)
            except HTTPError as e: # http status is not 200, continue next malcious url
                print(url, e)
                continue
cs

 

 

  마지막으로 해당 SQL Injection 의 시그니처를 만나게 되려면 에러가 발생시 데이터베이스 오류에 대한 자세한 에러 페이지를 보여줘야 한다. 에러시 공통 에러 페이지로 가게 되다면 안전하다고 판단 하는 오류를 가지게 된다(물론 SQL Injection 도 처리 안된 사이트라면 저렇게 라이브한 에러가 날 가능성도 높긴하지만 --;)

 

 

 

 

3. 상용 툴(또는 사람들이 많이 쓰는 오픈소스)과의 비교

  위의 간략한 POC 개념의 스캐너를 기준으로 상용툴과 비교하는 것은 좀 그렇긴 하지만, 어차피 상용툴도 첨에 이렇게 작은 프로그램 으로부터 시작되어 왔을꺼기 때문에 한번 비교를 해보자. 개인적으로 사용해 본 Aucnetix 를 기준으로 말해 보지만, 사용한지 몇년이 지났기도 하고, 툴마다 장단점들도 서로 있긴 한테니 참고만 하길 바란다. 

 

[Acunetix 페이지]

https://www.acunetix.com/ordering/ 

 

 

 

3.1 크롤링 측면 

  우선 크롤링 측면을 보자. 페이지 안의 링크에 걸린 페이지를 무조건 따라가는 것은 조금 위험한 측면이 있다. 사이트 외부로 링크가 나가게 되면, 외부 페이지의 링크를 다시 추적하게 되는 과정에서 과장한다면 www 전체를 돌아다닐 수도 있게 된다. 보통 스캐닝 툴에선 크롤링 시 따라가는 도메인의 제한과, 처음 페이지에서 들어가는 깊이의 제한으로 이슈를 풀어가는 듯 싶다. 

 

  또한 앞에서 얘기한 스크립트 기반으로 동적으로 생성이 되는 링크(폼 버튼을 누를 때의 validation 후  전송하는 스크립트 라든지),  AJAX 기반의 기능 호출의 경우는 단순히 HREF 만을 파싱해서는 찾아낼 수 있다. 좀더 자바스크립트 문법을 이해하는 정교한 크롤링 코드가 필요할 듯 싶다. 

 

 

 

3.2 스캐닝 측면 

  다음은 스캐닝 측면이다.

 

 

  첫 째, 스캐닝 측면에서는 차원의 축소 문제가 있다. 보통 큰 페이지에서는 인자로 취급될 수 있는 form 값이나, http header 값들이 수백개가 되는 경우가 있다. 그런 규모의 페이지가 100개라면 XSS 하나의 필터에 관해서만 500개의 테스트를 한다면 5만개의 리퀘스트가 날라가게 된다. 그런 필터가 수십개라면 몇 백만개의 쿼리가 날라가는 사태도 벌어지게 된다.

 

  그래서 얼마나 효율적으로 인자와 필터를 선택하고, 필터 안에서도 해당 도메인이나 페이지에 유효한 필터만을 날리느냐에 대한 문제가 있다. 그런 부분을 위해서 인자들을 정규식 패턴 등으로 예외처리 하거나, 해당 도메인에 의미있는 필터만을 선택하거나, 도메인 특성에 따라(예 ASP 라면 PHP 용 SQL Injection 을 날릴 필요가 없을 것이다) 자동으로 날릴 Payload 들을 선정 하기도 한다. 다만 해당 부분은 툴 자체에만은 맡길 순 없고, 도메인을 소스 레벨에서 이해한 상태에서 조정을 해야하는 부분 같다.

 

 

 둘 째, 앞과 연결되지만 크롤링된 모든 파일에 모든 필터를 꼭 점검해야 하느냐 하는 이슈가 있다. 점검할 가치가 없는 파일들을 적절한 도메인 지식하에 제외하거나, 특정 필터만을 돌리는 작업도 꼭 필요하다. 예를 들어 SQL 호출을 안하는 페이지에 SQL Injection 필터를 돌리는 것은 시간 낭비이다. 다만 그렇게 정교하게 필터 정책을 주는 것과 어차피 기계가 돌리는 거는 시간이 걸리더라도 덜 제외하는 데 필요한 노력을 들이지 않고 돌리느냐에 대해 유리한 쪽을 잘 판단해야 한다. 어디서나 ROI 문제는 생긴다.

 

 

  셋 째, 아무리 앞에 말한데로 조정을 하더라도 사이트가 크거나 복잡한 페이지가 많을 수록 필연적으로  많은 쿼리가 날라가게 됨을 피할 수는 없다. 그 경우 적절히 병렬로 쿼리를 날리면서도 부하 조절을 하는 등의 경감 정책도 필요할 것 같다(조그만 사이트는 DDOS 공격을 맞은 듯이 뻗을 수도 있고, 개발 팀이나 모니터링 팀에서 장애가 난다고 문의가 날라올 수도 있다)

 

 

  넷 째 필터에 포함된 쿼리가 얼마나 객관적이고 많은 이슈들을 담았느냐도 중요하다. 자질구레한 레벨의 실제 위협을 가져올 가능성이 거의 없어 수정하기 애매한 취약점들을 모두 찾기 위해서 모든 필터를 돌리는게 의미 있는지도 생각해 봐야한다(이건 사람마다 입장에 따라 의견이 다를 듯은 싶다). 상용 프로그램의 경우 상용이라는 부담감 때문에 기존에 나왔던 모든 이슈에 대한 필터가 너무 과도하게 진행되는 감도 있다. 특히 오픈 소스를 쓰는 것이라면 해당 스캐너의 동작을 깊진 않더라도 가능한 소스 레벨에서 이해해서, 앞에서 생기는 여러 문제들을 해결할 수 있도록 조정하고, 적절히 개조해 보려 하는 것도 나쁘진 않을 것 같다.

 

 

  다섯 째, 스캐너의 결과가 통과되었다고 100% 안전하다는 보장은 하진 못할수도 있다. 예를 들어 디폴트 패스워드나 디렉토리 검사를 통과했다고 하더라도, 사용자가 패스워드를 "안녕" 이라고 만들거나 폴더를 01022223333(본인 핸드폰 번호) 이런식으로 만들어 놨다면 스캐너가 통과됬다고 본질적인 디폴드 패스워드 개념에서는 안전하다고는 못할 것이다. 어찌보면 많은 필터들이 사전적 Brute Force 같은 성향을 가지고 있기 때문에 적절히 결과의 유효성을 판단해야 한다. 

 

 

  여섯 째, 비단 스캐너뿐 아니라 유명한 보안 상용 솔루션이나 장비를 쓰는 경우는 인증 및 감사 측면에서 유리한 면도 있는 듯 하다(감사시 ** 스캐너를 사용해서 매번 스캔하고 있어요 같이...). 실제로 많은 보안 장비가 해당 장비 자체가 이미 법이나 인증 획득에 필요한 기능을 구현해 인증 받아놓았기 때문에 여러 감사 측면에 유리하게 대응하기 위해서 도입 하는 경우도 있다. 다만 아무리 그렇더 라도 유명도나 인증, 레퍼런스가 해당 스캐너의 효율성과 등가라고는 할수 없기 때문에, 실무자 입장에서는 해당 필터가 정말로 의미있는 건지, 해당 디폴트 방식의 스캐닝이 의미 있는 건지는 잘 이해하고 따져봐야 한다.

 

 

  마지막으로 사이트에 로그인과 비로그인 시에도 미묘한 문제가 생긴다. 로그인 후 스캐너를 돌리다가 게시판 같은데 이상한 게시물을 잔뜩 달아 열심히 지워본 기억은 한번씩쯤 겪게 될 것이다. 반대로 로그인을 안한 상태에서 돌리게 되면 테스트 하는 페이지가 확연하게 줄어 실효성에 의문이 생길 수도 있다. 여러 상황에 맞춰 선택을 해야한다(테스트 환경에서만 로그인해 돌린다든지...).

 

  또한 스캐너가 날리는 페이로드는 기괴한 문법 문자가 많기 때문에, 당하는 프로그램 입장에서 에러처리가 안되서 프로그램이 죽거나 에러가 빵빵 나 버릴 수도 있다. 해당 부분은 페이로드를 바꾸던지 에러가 나는 쪽을 설득해 에러처리를 제대로 하게 해야한다.

 

 

 

4. 스캐닝 결과 검증 자체의 이슈

  위에서 봤듯이 스캐너의 원리는 적절한 인자를 찾아 페이로드를 보내고, 결과 페이지에서 내가 의도한 패턴을 찾는 것이다. 이것은 비단 웹 뿐만 아니라 네트워크나 다른 물리적인 스캐너도 마찬가지 이다(예를 들어 공항 투시 스캐너는 약한 X선이나 자기를 보내서 반사되는 형태를 기반으로 판단한다고 한다).

 

  앞의 스캐너 소스에서 봤듯이 어떤 가정을 하느냐에 따라서 결과가 의미가 있을 수도 없을 수도 있다. 막상 스캐너를 처음 돌려본 사람들은 쏟아지는 취약점 숫자에 깜짝 놀라다가도, 실제 의미 있는 결과인가를 체크해 보면서 스캐너가 얘기하는 수많은 False Postive 에 또 한번 놀라게 된다. 

 

  하지만 이건 스캐너가 판단 할수 있는 영역이 블랙박스라서 어쩔 수 없다고 봐야 된다. 사람처럼 블랙박스 테스트를 한다는 의미가 아니라, 리퀘스트와 해당 결과로 브라우저에 전달된(리스폰스) HTML 소스를 기반으로 열심히 판단하긴 하지만, 서버 내의 로직을 보거나 이해할 수 없다는 태생적 한계를 가지고 있다는 것이다. 다만 블랙박스 측면에서도 제대로 테스트를 하면 서버의 내부 로직을 유추할 수 있듯이, 스캐너도 필요한 측면에서 잘쓰면 마찬가지의 효과는 있다.

 

  또한 여기에서도 도메인, 프로그래밍적 지식이 필요한데, 왜 스캐너가 해당 페이지에서 해당 패턴의 취약점을 발견했다고 주장하는 지를 스캐너와 페이지 측면 양쪽 모두에서 이해하고, 유죄를 선고할지, 무죄를 선고할지를 판단할 수 있어야 한다. 해당 지식이 앞 시간에 나온 여러 주제들에 대한 지식에 추가해, 스캐너가 테스트를 할수 있다고 주장하는 필터에 대한 동작 원리에 대한 지식이다.

 

  많은 부분 판단이 힘든 경우 스캐너가 어떤 요청을 날렸고 어떤 결과를 받았는지 부터 자세히 살펴보는 것도 도움이 된다고 생각한다. 그게 스캐너가 바라보는 세상의 전부이니까 말이다. 

 

 

 

 

5. 수동 테스트(펜테스트)와의 차이

  그럼 사람들이 펜테스트 하는 방식이 이런 스캐닝 툴과 다른 점은 뭘까?

 

 

3.1 수동 테스트의 장점 

  첫 째, 6교시에서 얘기했던 업로드 같은 부분을 생각해보자. 페이지를 분석하고, 업로드할 악성 파일을 탑재하고, 프록시 단계에서 파일 이름이나 자바스크립트를 바꾸면서 이런저런 조작을 하는 부분을 스캐너 판단하에 자동으로 일어나도록 만들기는 참 어려울 것이다.

 

  일단 업로드 하기 전까지를 구현하는 것도 어렵지만, 업로드된 파일이 실제 어떤 경로에 저장되었음을 판단하는 것도, 업로드 후 업로드된 경로를 명시적으로 알려주는 사이트가 아니라면 자동으로는 많이 힘들 듯 싶다. 물론 100% 불가능 하게는 안보이지만 페이지에서 업로드 기능을 찾아내고, 웹 프록시를 이용하는 과정을 자동으로 구현한다고 생각하면 머리가 많이 아프다. 이런 측면은 수동 테스트가 휠씬 효율적인 부분이라고 생각한다.

 

 

  둘 째, 위와 비슷하게 여러개의 중간 단계를 거치면서 각 중간 단계에서 계속적으로 validation 을 하는 설계를 가진 페이지에는 적용하기 힘들다(요즘은 마이크로 서비스나, 도메인으로 나눠진 서비스가 많아서 페이지 호출이 복잡한 구조가 많다. 많이 가는 큰 사이트를 가서 피들러로 한번 살펴보면 하나의 페이지를 들어갈때 눈이 아플 정도로 많은 호출이 있을 것이다).

 

  위의 어려움 더하기 모든 validatio 포인트를 클라이언트 코드를 통해 회피해주는 작업이 필요하기 때문이다. 일반적으로 스캐너는 요청과 최종적으로 대상쪽에서 온 응답에 기반에 동작하기 때문에, 서버에서 일어나는 여러 단계의 중간 단계에는 관심이 없다. 사실 자동화의 복잡성 대응의 한계 때문에 그렇다고 보는게 맞을 듯도 싶다. 뭐 AI 를 이용한 보안 테스트가 많이 연구된다니 혹시 패턴이 많이 모인다면 나중엔 어쩔진 모르겠지만 말이다.

 

 

  셋 째, 마찬가지로 위와 연결되는 이슈인데, 수동 테스트는 블랙박스와 그레이, 화이트 박스의 조합을 적절히 취할 수 있다. 웹 호출을 보다가 데이터베이스의 변화를 관찰 할 수도 있고, 미심적은 처리 부분의 프로그램 소스를 열어서 볼수도 있다. 물론 자동화된 테스트도 디비를 참고하거나 프로그램 내부를 참조할수 있겠지만 그건 저런 스캐너 같은 도구는 아니고, 테스트를 위해 잘 디자인된 프로그램을 직접 제작하는 경우일 것이다. 또한 이해가 잘 안된다면 개발자와 의논할수도 있다^^.

 

 

  넷 째, 수동 테스트는 단순히 페이지의 분석에서 벗어나, 기존 도메인에 대한 지식과 경험이 녹아있는 테스트를 통해 실시간으로 전략을 바꿀 수도 있다. 이것은 언젠가 AI 가 따라 잡을 분야일진 모르겠지만, 지금 상태로는 죽기전엔 구경 못할 듯은 싶다.

 

 

 

3.2 자동 테스트의 장점 

  첫 째, 자동은 우선 빠른 실행 속도가 장점이다. 서버의 부하나 에러가 허용되는 만큼 병렬로 돌릴 수도 있고, 명확히 문제와 검증 방식이 정의된 영역 부분에 대해서는 장점이 있다.  

 

 

  둘 째, 파이썬 마지막 교시에 얘기했던 five orders of ignorance 에서 얘기한것 처럼 모든 소프트웨어는 지식의 결정체 이다. 솔루션이라는 것은 우리가 가진 보안 지식을 정리하여 소프트웨어 형태로 만들어, 컴퓨터의 파워를 이용해서(어찌보면 밝음을 구현하는 손전등을 위해 건전지를 사용하는 것과 비슷하다) 자동화된 지식을 반복적으로 재사용 하는 것이다.

 

  지식이나 물리적인 힘(물리적 장치와 연결되면 힘의 움직임도 에너지는 다르겠지만 재사용 할수 있다)을 재사용 한다는 측면에서의 자동화는 정말 우수한것 같다. 여러 공장 자동화들의 예에서 이런 것들을 많이 볼수 있고, 몇몇 천재들이 자신의 지식을 잘 담아서 상용화 시킨 편하고 감탄이 나는 소프트웨어들을 많이 볼수 있다.

 

 

  셋 째, 변경이 적은 지식의 영역에서는 일단 세팅이 완료되면 우수한 ROI 가 이루어 진다. 예전 회사에서 같이 일하던 사람이 여러 나라의 언어로 이루어진 설치 프로그램을 자동화 툴을 이용해 검증해 돌렸는데, 한번 만들어 놓으면 약간의 코드 수정만으로 본전을 뽑는 다는 느낌이 들었었다. 게다가 자꾸 만들다 보면 자신만의 라이브러리도 쌓이고, 이미 해결해 놓은 지식들이 많기 대문에 ROI 의 I(Investment) 부분이 점점 줄어들 것이다. 

 

  IT 쪽에서 사람들을 겪어 본 사람들은 프로그래머나 다른 인력이나 사람들마다 많은 속도나 품질, 타인의 대한 좋은 태도의 차이가 난다는 것을 많이 느꼈을 거다. 자신이 평균 이상의 속도나 품질, 타인의 대한 좋은 태도를 가지도록 계속 노력해 보자.

 

 

  넷 째, 객관적인 증명이 된다. 사람은 피곤하면 잘못 보기도 하고, 실수도 하고, 슬쩍 해야될 점검을 모르는 척 빼먹을 수도 있지만(생각보다 종종 일어나는 일이다),  기계의 장점은 항상 객관적이라는 것이다. 물론 설계를 잘못했거나 어설프게 검증하도록 만들었을 경우에도 지나치게 객관적이여서 문제다(gabage in garbage out 이 여기도 적용된다). 앞에서 얘기한 소프트웨어를 구성해야할 올바른 지식들이 필터나 스캐너 프로그램의 철학에 잘 담겨져 있는지를 꼭 확인해 보자. 

 

 

 

3.2 자동 테스트 vs 수동 테스트 

  이건 "엄마가 좋아 아빠가 좋아" 문제이다. 서로의 단점을 보안해줄 좋은 수단이 동시에 있는데 굳이 하나를 외면 해야할 필요가 있는가? 수동 테스트에 익숙한 사람은 스캐너를 잘 다루도록 노력하고 자기만의 스캐닝 정책을 커스터마이즈 하거나, 불편한 부분을 개선하거나 하는 노력을 해야한다. 스캐너는 어찌보면 수동 테스트에서 귀찮았던 부분을 해결하기 위해 만들었기 때문에 업무의 중요한 부분에 집중하는데 많은 도움이 될것이다.

 

  반대로 스캐너에 주로 익숙한 사람들은, 테스트 페이지를 제작해 스캐너가 날리는 쿼리에 대해 웹서버 로그로 저장하거나 피들러로 살펴보면서, 하나하나의 필터가 하는 일을 명확하게 이해해야 한다. 해당 부분을 이해하는 과정에서 결국은 스캐너의 한계를 깨닳고 수동 테스트로 어떻게 보강을 해야될지를 고민하면서 자동으로 공부를 많이 하게 될 것 같다.

 

  양쪽 다 서로 자신을 알면 알수록 상대방을 잘 이해할 수 있고, 상대방을 잘 이해할 수록 자신의 일도 더 잘 이해할 수 있게되는 측면이 있다고 본다.

 

 

 

 

6. 마무리 하면서

  스캐너 쪽이나 펜 테스트 쪽이나 아주 깊게 아는 편은 아니기 때문에, 어설프게 얘기하거나 빠뜨린 부분이 있는지도 모르겠다. 하지만 결국 소프트웨어는 지식을 담아 재사용하는(나아가 제작의도와 다르게 응용해 사용하기도 하는) 부분이기 때문에 스캐너를 해당 관점으로 보면서 스캐너 자체의 빤짝거림 보다는 안에 담긴 지식의 알맹이들에 관심을 가졌음 하는 바램을 가지며 이 글을 마치려 한다.

 

 

 

2019.5.23 by 자유로운설탕
cs

 

 

 

 

 

 

posted by 자유로운설탕
2019. 5. 6. 16:50 보안

  보안 설계(security by design) 문제 부분도 설명하기는 좀 애매한 분야이기는 하지만 1~9교시가 모두 종합된 분야라고 볼수 있고, 앞으로 진행될 모든 사항들에도 해당될 듯한 주제이니까, 여기 쯤에서 한번 언급하고 뒤를 진행하는게 맞을 것 같아서 넣게 되었다.



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 


 


1. 들어가면서

  보안 설계 문제는 어떻게 보면 보안 문제를 예방하기 위한 가장 첫번째 단계라고 볼수 있다. 앞의 모든 시간에 다루었던 주제들을 각각 다른 취약점 타입으로 볼수도 있겠지만, 사실 모든것은 상호 연관되어 있기도 하고, 동시에 일어나기도 하며, 각각의 측면을 종합해서 접근해야 할 필요가 있다. 마치 운동을 배울때 기초체력 부터 하나하나 기술을 배우기는 하지만 결국은 실전에서는 모든게 한꺼번에 조합되어 필요한 것과 마찬가지라고 생각된다.

 

  그래서 하나하나의 주제에 대해서 주의 깊게 바라 보는 것도 중요하지만, 항상 모든건 포괄적으로 움직이는 시스템을 위한 것이라는 것을 잊으면 안된다. 업로드에 대한 보안지식은 업로드 기능이 없는 시스템에서는 생각할 필요도 없는 것처럼(하지만 기술은 보통 다른 기술을 많이 참조하기 때문에 이해한 원리는 다른 기술을 이해할때 도움이 된다), 보안은 대상 자체가 없으면 아무 의미가 없다.

 

 

 

  또한 설계라는 것도(조금더 고급스럽게 얘기하면 아키텍쳐) 사실 어딘가에서 빵 나타나는 고급스러운 존재는 아니라고 본다. 하나하나 작은 기술의 경험이 합쳐져서 전체를 이루는 흐름을 파악할 수 있게 될때, 설계라는 일종의 선택 가능한 패턴으로 만들어지는 것이라고 본다. 고급 스러운 설계 작업이나 일반 적인 설계나 멀리 떨어진 시각에서 보게 되면 도토리 키재기 일수도 있다.

 

 

 

 

2. 보안 설계가 연관된 부분

  해당 문제는 여러 분야에 관련되어 있을 수 있으며 종종 버그라 불리우며 QA쪽 영역과 오버랩 되기도 한다(어떤 회사들은 보안 쪽 관련된 팀이, 어떤 회사에서는 QA 쪽 관련된 팀이 연관될 수도 있을 같다). 

 

  간단한 예로는 모바일 게임등에서 종종 나타나서 게임사를 당황하게 하고 유저의 신뢰를 떨어뜨리는 이벤트 중복 참여 라든지, 아이템 복사 등 부터, 서버와 클라이언트 상의 왔다갔다 하는 호출들의 보호문제, 비밀번호 찾기나 본인인증 창 설계, 주문서 설계 등이 있다.  크게는 하나의 도메인(회원, 결제, 등록) 등의 전체적인 흐름을 잡아야 할 경우도 있을 거고, 작게는 설치되는 여러 데이터베이스나 서버등의 설정 및 호출 방식등이 연관될 수 있다. 또한 외부 회사와의 전문, 키 교환 등의 상호 연동 부분도 있을 수 있다. 추가로 물리적인 보안 부분도 맡고 있는 팀도 있긴 하지만 그건 여기서는 좀 별개로 하려 한다.

 

 

 

2.1 간단한 설계예제

  그럼 어떤 움직이는 예제를 보이면 좋을까 생각하다가, 은행이나 웹 사이트 등에서 특정한 행동을 하기전에 본인인증 수단으로 사용하는 핸드폰 전송 문자를 입력 후, 결제를 하는 예제를 간단하게 만든 후, 보안적으로 잘못된 부분을 패치해 보려고 한다.

 

 

  아래와 취약한 플라스크 코드의 예제를 한번 보자. 먼저 design 이라는 경로를 호출하게 되면 flask_design.html 템플릿 파일이 읽혀진다(이것은 뒤에서 본다).

 

  checkSMS 는 넘어온 인자 중 smsNum 이라는 이름이 "7777" 이라는 값이면(문제를 간단히 하기위해 핸드폰에 "7777" 이라는 문자가 전송되어 왔다고 가정한다), Ture 값을 아니라면 False 값을 보낸다. 적절한 핸드폰 문자를 받지 못한다면 서버 쪽에서 체크하는 로직이기 때문에 이 부분을 통과할 수 없을 것이다. 

 

  doPayment 는 넘어온 price 값을 따로 쓰진 않고^^ True 값을 바로 호출한다. 원래 대로라면 금액은 맞는지, 잔고는 있는지 등등을 체크를 하고, 데이터베이스에 이런 저런 데이터를 넣어 결제를 해야 하지만, 간략히 하기위해서 해당 부분을 처리 했다고 하고 결과만을 성공으로 리턴한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from flask import Flask, render_template, request, jsonify
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/design", methods=['GET'])
def design():
    return render_template('flask_design.html')
 
 
@app.route("/checkSMS", methods=['GET'])
def checkSMS():
    sms_num = request.args.get('smsNum')
    if sms_num == "7777":
        result = "True" 
    else:
        result = "False"
    return jsonify({'var1': result})
 
@app.route("/doPayment", methods=['GET'])
def doPayment():
    price = request.args.get('price')
 
    result = "True" 
    return jsonify({'var1': result})
 
 
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

[flask_design_before.py]

 

  c:\python\code 폴더에 UTF-8 포맷으로 flask_desing_before.py 라고 저장한다.

 

 

 

  다음으로 템플릿 파일 쪽을 보자. 처음 로딩 되면 인증번호 입력 하는 텍스트 박스가 있고 인증하기 버튼이 있다. 그 밑에는 금액과 결제하기 버튼이 보인다.

 

  그 중간에  있는 히든필드인 id 가 isCert 인 필드가, SMS 인증이 됬는지를 몰래 숨겨 가지고 있는 중이다. 해당 히든필드가 필요한 이유는 페이지가 일단 로딩 되게 되면 그 후에는 DOM 과 자바스크립트의 세상이기 때문에, 인증이 됬는지 안됬는지를 판단하기 위해서 어딘가에 값을 가지고 있어야 한다(물론 어떻게 만드느냐에 따라서 결제하기 버튼을 눌렀을때 서버 쪽으로 AJAX 호출을 해서 인증 했는지 체크하고, 이후 인증 창을 보여줄수도 있지만, 앞의 클라이언트 코드에서 봤듯이 조작하는 수준은 비슷하다).

 

  자바스크립트 쪽을 보면 "인증 번호 입력" 버튼(id = checkData)를 눌렀을 때, checkSMS API를 호출하면서, 사용자가 입력한 smsNum 값을 넘겨준다. 결과 값이 True 로 오면 isCert 히든 필드 값을 "Y" 로 바꾸고, 인증 완료하고 표시해 주며, 아니라면 다시 입력해 달라는 문구를 표시한다.

 

  "결제 하기" 버튼을 누르면 isCert 값을 체크하여 디폴트 값이 N 그대로 라면 SMS 인증을 받으라는 Alert 을 띄우고, 만약 앞에서 미리 인증 번호를 넣어서 Y 로 바뀌어 있는 상태라면, 바로 doPayment API 를 호출해 결제를 하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<html>
  <head>
    <script src="http://code.jquery.com/jquery-3.3.1.min.js" ></script>
      <title>Call API</title>
      
    <script type="text/javascript">
      $(document).ready(function(){
 
        $("#checkData").click(function() {
          var smsNum = $("#SMSNumber").val(); 
           
          $.ajax({
            url: "/checkSMS",
            data: { smsNum : smsNum },
            contentType: 'application/json;charset=UTF-8',
            success: function(data){
              if(data.var1 == "True"){
                  $("#smsResult").css("background-color","orange");
                  $("#smsResult").html("인증완료");
                  $("#SMSNumber").attr("disabled""disabled");
                  $("#checkData").attr("disabled","disabled");
                  $("#isCert").val("Y");
              }
              else{
                  $("#smsResult").css("background-color","red");
                  $("#smsResult").html("다시 입력해 주세요");
              }
            }
          });
        });
        
        $("#doPayment").click(function() {          
          var isCertified = $("#isCert").val();
          
          if(isCertified != "Y")
          {
            alert("SMS 인증을 먼저 받으세요...");
            return false;
          }
          
          var price = $("#price").val();
          
          $.ajax({
            url: "/doPayment",
            data: { price : price },
            contentType: 'application/json;charset=UTF-8',
            success: function(data){
              if(data.var1 == "True"){
                $("#paymentResult").html("결제 완료");
                $("#paymentResult").css("background-color","cyan");
              }
              else{
                $("#paymentResult").html("결제 실패");
                $("#paymentResult").css("background-color","cyan");
              }
            }
          });
        });
      });
 
    </script>
  </head>
  <body>
    <table>
      <tr>
        <td><input type="text" id="SMSNumber"</td>
        <td><input type="button" id="checkData" value="인증 번호 입력"></td>
      </tr>
      <tr>               
        <td><span id="smsResult"></span></td>
        <td><input type="hidden" id="isCert" value="N"</td>
      <tr>
    </table>
    <table>
      <tr>
        <td><input type="text" id="price" value="1000"></td>
        <td><input type="button" id="doPayment" value="결제 하기"></td>
      </tr>
      <tr>
        <td><span id="paymentResult"></span></td>
      </tr>
    </table>
  </body>
</html>
 
cs

[flask_design.html]

 

  c:\python\code\templates 폴더에 UTF-8 포맷으로 flask_design.html 이름으로 저장한다. 이후 cmd 창에서 실행 시킨다.

c:\Python\code>python flask_design_before

 

  브라우저에서 http://127.0.0.1:5000/design 주소를 입력하고, 결제하기 버튼을 바로 누르면 앞의 스크린 샷과 같은 얼랏 창을 보게 된다.

 

 

  위의 로직을 대충 도표로 그리면 아래 정도가 아닐까 싶다.

 

 

 

 

2.2 해당 설계의 문제

  해당 설계의 문제는 무엇일까? 앞의 클라이언트 코드 쪽을 이해한 경우 쉽게 보안적으로 취약한 부분이 무언지 알수 있다. isCert 히든필드는 사용자에게 Alert 안내를 하기위한 자바스크립트에서는 유용하다. 하지만 해당 값은 "인증 번호 입력" 버튼을 눌러 AJAX 호출을 이용해 수정하는 값이므로, 브라우저 개발자 도구나, 피들러 등으로 결제 하기 버튼을 클릭하기 전에 임의로 "Y"로 수정해 버린다면, 인증번호 체크를 안한 상태로 결제가 가능하다(또한 스크립트 조작 때 처럼, 체크하는 스크립트를 아예 제거해도 된다).

 

  한번 피들러로 조작을 해보도록 하겠다. 피들러를 킨채로 Rules > Automatic Breakpoint > After Responses 를 선택한다(조작 방법이 기억이 안나면 5교시를 다시 보고 오자).

 

  이후 페이지를 로드하고, 피들러가 리스폰스를 잡은 상태에서, 오른쪽 하단의 Response 섹션에서,  Textview 탭을 클릭 후, isCert 히든필드 값을 그림과 같이 "Y" 로 바꾸어준다. 이후 Run to Completion 버튼을 눌러 브라우저로 데이터를 전송해 준다. (위의 브레이크 포인트는 이제 다시 Disabled 로 바꾸어 풀어준다).

 

 

  이제 브라우저 소스를 보면 해당 값이 "Y" 로 바뀌었기 때문에, 인증 번호 입력 없이도 아래와 같이 결제가 되어버린다. 이런 스타일의 조작은 팝업 형태로 띄워지는 통신사 등의 외부 본인 인증 창의 회피 등에서도 마찬가지로 쉽게 적용할 수 있다.

 

 

 

 

2.3 설계 패치하기

  그럼 해당 설계의 문제는 무엇일까? API 로 SMS 문자를 체크한 부분은 서버 사이드 인증이 맞지만, stateless 인 웹의 특성 상, 두번째 API 호출인 결제하기에서는 SMS 문자를 제대로 체크했는지를 기억하지 못한다는 것이다.

 

  사실 보안 설계의 재밌는 점 중 하나는 이러한 문제를 해결하는 방법이 하나는 아니라는 것이다. 여러가지 등가적인 안전한 방법을 만들 수 있고 그 중 가장 현재 코드나, 설계, 주어진 리소스에 적절한 방법을 선택할 수 있다. 하지만 등가적인 부분의 원리는 모두 같다고 보면 된다(물론 가끔은 행운?을 바라며 차선을 선택할 때도 있다).

 

  위의 예에서는 첫번째 API 안에서 SMS 문자를 인증 후, 공격자가 건드릴 수 없는 어딘가에 해당 사실을 저장하고, 결제 API 로직 안에서 해당 정보를 체크해서, 현재 사용자가 올바르게 인증한 사용자인지를 검증하는 방법을 쓸수 있다. 해당 검증 정보는 암호화된 쿠키(재사용을 경계해야 한다)나, 데이터베이스나, 서버 단(AJAX가 아니다) 로직에서 호출해서 체크할 수 있는 다른 API 의 어딘가 내부 공간 등에 자유롭게 저장할 수 있겠지만, 보통 데이터베이스를 이용하는 것이 제일 간단할 듯 싶다.

 

  아래의 수정된 flask_design_after.py 파일을 보자. 템플릿 파일은 어차피 클라이언트 코드므로 서버 사이드 로직이 들어있는 쪽을 수정하면 될듯 싶다. 우선 보면 3교시에서 사용했던 MSSQL 서버를 그대로 이용한다. 하나 가정하는 것은 원래 어떤 사용자 인지는 랜덤 값이 섞여 암호화된 쿠키를 베이스로 가져와야 변조가 안되지만 여기서 로그인 쿠키 코드까지 고려하면 복잡해 지니 암호화된 쿠키를 가져와 현재 사용자인 tom 을 member_id 라는 변수에 넣은걸로 하자^^ 

 

  checkSMS 쪽을 보면 사용자가 입력한 번호가 맞다면, sms_cert 라는 테이블에 ("사용자 아이디", "Y", "인증시간") 을 넣어주는 것을 볼수 있다.

 

  이후 doPayment 쪽에서는 sms_cert 테이블을 뒤져 사용자 아이디가 현재 로그인한 "tom" 이고, 인증 결과가 "Y" 이고, 인증 시간이 15분 이내 인지를 체크한다. 인증 시간 조건을 넣은 이유는 어제 인증을 했는데도 인증 했다고 판단하면 공격자가 다음날 공격해도 성공이 되기 때문에, 사용자가 인증하자마자 15분내에 공격 받는 일은 거의 없다고 보자. 저 시간을 너무 짧게하면(예를 들면 1분) 사용자가 인증 후 결제 버튼을 누를까 말까 고민하다가 1분이 지나면 인증이 안됬다고 나오는 사용성 문제가 생기게 된다. 

 

  만약에 해당 15분 간격도 맘에 걸린 다면 조금 더 나아가 SEQNO 같은 고유값을 sms_cert 에 추가해도 괜찮을 거 같다. 그럼 공격자가 15분내에 시도 한다고 해도, 이전 사용자가 인증 받을때 사용한 SEQNO 를 모른다면, 맞는 SEQNO 를 찾아내기는 거의 불가능할 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from flask import Flask, render_template, request, jsonify
# 모듈을 불러옵니다.
import pyodbc
import datetime
 
# 연결 문자열을 세팅합니다.
server = 'localhost'
database = 'mytest'
username = 'pyuser'
password = 'test1234'
 
# 데이터베이스에 연결합니다.
mssql_conn = pyodbc.connect('DRIVER={ODBC Driver 13 for SQL Server};SERVER='+server+'; \
    PORT=1433;DATABASE='+database+';UID='+username+';PWD='+ password)
 
# 커서를 만든다.
cursor = mssql_conn.cursor()
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/design", methods=['GET'])
def test_search():
    return render_template('flask_design.html')
 
 
@app.route("/checkSMS", methods=['GET'])
def checkSMS():
    sms_num = request.args.get('smsNum')
    now_time = datetime.datetime.now()

    member_id = "tom"

    
    if sms_num == "7777":
        result = "True" 
        cert_sql = "insert into sms_cert values (?,?,?)"
        cursor.execute(cert_sql, member_id, "Y", now_time)                
        mssql_conn.commit()
    else:
        result = "False"
    return jsonify({'var1': result})
 
@app.route("/doPayment", methods=['GET'])
def doPayment():
    price = request.args.get('price')
    member_id = "tom"
    before_15minute_time = datetime.datetime.now() - datetime.timedelta(minutes=15)
    
    check_sql = "select top 1 cert_yn from sms_cert(nolock) where member_id = ? and cert_date >= ?"
    cursor.execute(check_sql, member_id, before_15minute_time)   
    cert_yn = cursor.fetchone()
  
    if cert_yn:
        result = "True" 
    else:
        result = "False"
    return jsonify({'var1': result})
 
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

[flask_design_after.py]

 

 

  실제 테이블을 하나 만들어야 하니 위와 같이 MSSQL Management Studio 를 이용해서 아래와 같이 sms_cert 테이블을 생성한다(사용법을 모를 경우 파이썬 4교시로...).

1
2
3
4
5
CREATE TABLE [dbo].[sms_cert](
    [member_id] [char](20NOT NULL,
    [cert_yn] [char](1NOT NULL,
    [cert_date] datetime NOT NULL,
)
cs

 

 

 

 

  이후 패치한 파이썬 코드를 실행 한 후, http://localhost:5000/design 페이지를 띄운다.

c:\Python\code>python flask_design_after.py

 

 

  앞의 예제와 같이 피들러로 클라이언트 코드를 회피한 경우에는 여전히 Alert 창은 뜨지 않고 결제까지 넘어가지만, 결제 API 에서 데이터베이스 조회를 통해 인증이 안 됨을 체크해서 막히게 된다. 상황에 따라 저 메시지를 애매모호하게 만들어 왜 막혔는지를 감추어 공격자를 헷깔리게 할 수도 있다.

 

 

  아래의 도표가 위의 보강된 로직을 나타낸 것이다. 이렇게 되면 앞에서 아무리 클라이언트 코드를 공격하더라도 문제가 없다. 뭐 그래도 넓은 측면에서 보면 핸드폰 문자가 탈취되게 되면 어플리케이션으로 막을 방법은 없고, 이후부터는 FDS 같은 결제 모니터링 시스템으로 잡을 수 있는 패턴이기만을 바랄 수 밖에는 없다.  

 

 

 

 

3. 보안 설계 정리하기

  앞에서 간단하지만 종종 만날수 있는 하나의 패턴을 살펴보았다. 그럼 보안 설계를 하는데 중요한 포인트는 무엇일까? 계속 하는 얘기지만 가장 중요한 것은 보호할 대상을 이해하는 것이다.

 

  만약 우리가 미술관의 보안을 책임지고 있는 사람이라 해보자. 그럼 기본적으로 미술관 자체의 운영에 대한 이해, 미술품 자체에 대한 이해, 미술품 도둑들의 심리의 이해를 어느 정도 해야지 무엇을 할 수 있을지 알게 되지 않을까 싶다. 제한된 예산이 주어졌을 때 가장 가치가 있는 미술품들을 어떻게 지켜야 하는지도 결정할 수도 있고 말이다.

 

  마찬가지도 어플리케이션 도메인에 대해서 설계를 잘하려면 우선 현재 방어해야될 대상인 어플리케이션이 동작하고 있는 방식을 이해하는 것이 가장 중요하다. 어찌보면 보안적 패턴에 대한 고민은 그 다음일지도 모른다. 보안 설계는 도메인 전체 설계의 애드온 같은 측면으로 봐야한다. 물론 보안 쪽 일을 하는 입장에서 쉽게 인정하기 싫은 측면이긴 하지만, 넓은 측면에서 보면 보안은 항상 전체 뷰의 한 측면뿐이라는 것을 잊으면 안된다. 사람도 마찬가지로 스스로를 객관적으로 보기는 참 힘들지만, 스스로를 객관적으로 보기 시작했을때 좀더 정확한 판단을 내릴 수 있게 된다.

 

  모의해킹과 비교해 보자면, 모의해킹이 현재 적용된 설계의 문제있는 부분을 증명하는 작업이라면(앞에 얘기했듯이 문제 없음을 증명하는 건 몇백배 더 난해한 일이다), 보안 설계는 다른 측면에서 설계 자체가 문제 있게 되지 않도록 방지하는 측면이 있다. 두 개를 상호 보완적으로 서로를 잘 커버해 주도록 배치해서 사용한다면 좋을 것 같다.

 

  다른 측면에서 여러 가이드나 표준을 보다보면 이러한 패턴화된 보안 설계들을 명문화 하려는 노력이 들어간 느낌을 받게 된다(다만 조각조각 파편화는 되어있다). 권고된 무언가를 수행 해야되는 입장이 주어졌을때, 해당 상황에 대해 무조건적으로 받아들여 적용하는 것보다, 어떤 설계 의도로 주어지게 된 것인지를 생각해보고, 기존 설계 안에 잘 조화되게 넣는 것도 중요한 것 같다.

 

 

 

 

4. 마무리 하면서

  점점 일을 할 수록 모든 IT 분야의 일을 하는데 있어서 가장 중요한 것은 "기본"이라는 생각이 든다. 기본 이라는 것은 "기초적인 지식"의 의미라기 보다는, "해당 기술이 나오게된 원인과 배경을 잘 이해한다"는 의미에 가까운 것 같다. 자신만의 관점을 가지게 되고, 그 관점으로 해당 분야를 해석하기 시작했을 때부터, 정말 그 분야에 발을 들이기 시작하게 되는 것이라고 생각한다. 그런 의미에서 보안은 그렇게 평생 공부해야될 기본 지식들이 무수히 많은 괜찮은 분야인것도 같다. 가끔 길을 잃고 힘들어 머뭇거리더라도 멈추진 말고 계속 앞으로 갔으면 한다. 이건 스스로에게 건내는 말이기도 하다^^

 

 

 

2019.5.12 by 자유로운설탕
cs

 

 

 

 

 

 

posted by 자유로운설탕
2019. 5. 1. 18:52 보안

  하드닝(Hardening) 보안을 "단단하게" 만든다는 의미로 앞에 나온 여러 이슈들을 포괄하면서도 프로그램 이외의 환경이나 설계적인 요소까지도 포괄하는 주제이다. 어찌보면 각종 보안 가이드 문서에 나와있는 ~을 하라는 모든 내용의 시작점 이기도 하다. 너무 포괄적이여서 어떻게 얘기를 풀어가야 할진 잘 모르겠지만, 일단 설명을 시작해 보려한다. 



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터



1. 들어가면서

  하드닝이란 이슈는 꼭 보안 업무에만 관련된 것만은 아니다. 예를 들어 우리가 집에 새로운 와이파이 공유기를 설치 했을때에도 해당 이슈를 마찬가지로 만나게 된다. 공유기 설정을 맘대로 바꾸는게 가능한 admin 계정의 패스워드는 어떻게 할것인지, WIFI 통신의 암호화는 어떤 방식으로 할것 인지, 접속할 수 있는 기기들의 MAC 주소를 지정해 관리할 것 인지 등등 여러가지 설정상의 보안 문제들을 고민하게 된다. 요즘 많이 설치하는 IP 카메라 같은 경우도 마찬가지 이고 말이다. 

 

  물론 해당 부분을 모두 다 고려한다고 해도 100% 안전하다는 보장은 못하겠지만, 100% 안전하지 못할 상황만은 피할 수 있다고 본다. 마찬가지로 운영등에 사용할 OS, 웹서버 설치, 데이터베이스 설치, 어플리케이션 배포 등에 대해서도 마찬가지로 전문가들이 보안적으로 안전하다고 생각하는 사례들이 있다. 물론 해당 항목은 기술적이나, 보안적 측면으로 내린 근거에 기반한다고 생각하는게 맞고, 해당 부분을 일종의 보안적 패턴이라고 봐도 된다.

 

 

 

2. 몇가지 패턴으로 분류해 보기.

  개인적으로 무언가를 단순히 외우는 것은 아무 의미가 없다고 생각하기 때문에, 의미를 부여하기 위해 많이 권고 되는 몇가지 하드닝 사항들을 특정 패턴으로 한번 묶어 보도록 한다.

 

 

2.1 디폴트 배제

  보안에서 최초 기본적으로 나오는 디폴트 설정 문제다. 위에 있는 공유기 어드민 암호도 마찬가지다(바꾸라는데 왜 바꾸어야 하는지 모르거나 귀찮아 하는 사람이 많기 때문에, 근래의 공유기들은 무조건 최초에 기본 admin 패스워드를 변경해야지만 사용할 수 있도록 변경되었다. 하드웨어 적으로 초기화를 시킬수 있는 부분이 있긴 하지만 뭐 패스워드 잃어버려 공유기를 못설정하는 일이 생기면 안되는 부분이니 일종의 현실과의 협상이라고 보면 될듯 하다). 또한 디폴트 패스워드 뿐만 아니라, 프로그램을 처음 설치했을때 정의되어 있는 설정들이 보안적으로 바람직하진 못한 경우도 포함된다.

 

 

 

  이 카테고리에 속하는 것들이 많이들 언급 되는 디렉토리 리스팅, 너무 상세한 에러 페이지, 유추할 수 있는 관리자 페이지 URL 경로, 장비나 오픈소스, 솔루션 어플리케이션의 기본 패스워드, 백업, txt 파일등의 다운로드 문제, 톰캣 관리자 페이지, 패스워드 없이 접근되는 NoSQL 데이터베이스 등이 있을 수 있다. 모의해킹이나 시큐어 코딩, 해킹, 보안 설정 관련된 책들에서 주로 많이 나오는 내용들이다. 웹 검색 등에서 찾아보면 아래의 페이지 들이 그런 주제들을 간단하게 설명하고 있다. OWASP 같은 페이지를 검색해도 될것 이다. 디렉토리 리스팅 같은 문제는 Flask 같은 예제를 보면 전통적인 디렉토리 안에 파일이 있는 방식이 아니기 때문에 기술의 변화에 따라 뭔가 판이 달라지는 측면도 보인다.

 

[directory listing 취약점 막기(Apache2 보안) - intadd 님 블로그]

https://intadd.tistory.com/97

 

[톰캣 에러페이지 설정(정보 및 버전 감추기) - Mr.lee 님 블로그]

https://lee-mandu.tistory.com/327

 

[구글 해킹 유용한 명령어 - 정보보안 기록 저장소 님 블로그]

http://coashanee5.blogspot.com/2017/02/blog-post.html

 

[갈수록 중요해지는 권한 계정 보안 - 보안 뉴스]

http://www.boannews.com/media/view.asp?idx=51321

 

[홈페이지 보안 취약점 - 취약한 파일 존재 - IT 보물창고님 블로그]

https://skynarciss.tistory.com/29

 

[MongoDB 인증 모드 설정 - kkd927 님 블로그]

https://itstory.tk/entry/MongoDB-%EC%9D%B8%EC%A6%9D-%EB%AA%A8%EB%93%9C-password-%EC%84%A4%EC%A0%95

 

 

  이 하드닝 부분의 애매한 측면 중 하나가, 시간이 점점 지나면서 소프트웨어 보안에 대한 인식이 점점 강화 되고 있기 때문에, 앞의 공유기의 예처럼 새로운 버전의 OS 나 솔루션의 경우 많은 기본적으로 방어되도록 배포 되고 있다는 것이다. 예를 들어 앞에서 설치한 윈도우 10에서 설치되는 IIS 7.5 같은 경우에는 기본적으로 디렉토리 리스팅 기능이 OFF 되어있고, txt나 bak 확장자 파일의 경우도 URL 로 접근하면 예전과는 달리 파일이 실제 있더라도 404.4 에러가 난다. ASP 예제를 위해 IIS 를 설정할때도 생각해 보면 에러 또한 디폴트로 자세한 에러를 내보내지 않도록 설정되어 있다. 또한 기본적으로 웹서버나, FTP 서버가 설치되어 있지 않고 명시적으로 사용자가 설치해야 한다.

 

  하지만 보안 쪽의 귀찮음은 세상하는 항상 오래된 시스템이 있고, 해당 시스템은 잘 파악도 되지 않는 경우가 많기 때문에, 최신 환경에서는 일어나기 힘든 여러 과거의 취약점 패턴들에 대해서도 인지하여, 돌다리도 두드려 가는 마음으로 체크를 해야한다는 것이다. 다행이 이쪽은 스캐너 같은 도구나 스크립트 등이 도와줄 수 있는 측면이 많다고 본다.

 

  반대로 해킹을 해야되는 사람들 입장에서도, 모든 장비나 시스템이 최신 OS 로 패치되어있고, 하드닝이나 보안 설계가 잘 되어 있다면, 정말 아무도 모르는 패턴을 먼저 찾아낼 수 있는 소수의 능력자만 먹고 살수 있을지도 모른다. 하지만 세상일은 대부분 사람이 포함되어 하는 일이고 실수나 무지가 존재하기 때문에 항상 적정한 균형이 맞춰 지는 것 같긴하다. 이번 윈도우즈 10 업그레이드 이슈만 봐도 비용과 호환성의 측면이 있기 때문에 XP 처럼 쉬워 보이진 않는다. 아직도 어떨 수 없이 XP 를 쓰는 환경도 가끔 있다. 이런 면에서 보면 모바일 쪽에서는 IOS 쪽이 자유도는 엄청 떨어지긴 하지만 보안이나 유지비용 측면에서는 현명한거 같기도 하다. 

 

 

 

2.2 불필요한 것들 걷어내기

 

  다음 측면은 보안의 다른 철학 중 하나인 필요없는 사항을 걷어내는 것이다. 업로드 폴더의 실행권한을 제거하거나(또는 업로드 폴더를 웹서버 루트 폴더 바깥으로 빼기도 한다), 서버에 백업이나 테스트 파일을 남겨놓지 않는다든지(개념상 2.1과 겹치기도 한다), 웹게시판 이나 PHP 등의 샘플 파일을 제거한다든지, 사용하지 않는 특정 서비스를 Diable 시킨다든지 하는 주제가 포함된다.

 

  이 부분도 앞에 얘기했듯이 OS 나 취약점이 있었던 대상 프로그램이 업그레이드 되면서 일어나지 않게될 가능성이 높아져 스캐너에게 임무를 양보해야 할 과거의 지식이 될 가능성이 높다. 위와 같은 예제를 웹에서 찾으면 아래와 같을 것이다.

 

[파일 업로드 취약점 점검 및 보안 - blackhyuk 님 블로그]

https://bbhyuk.tistory.com/88

 

[FCKeditor 취약점 + 간단실습 -RedScreen 님 블로그]

https://redscreen.tistory.com/69

 

[서비스 관리 - 불필요한 서비스 제거 - IT 보물창고님 블로그]

https://skynarciss.tistory.com/190

 

 

 

2.3 필요한 정도로만 권한을 부여하기

 

 

  다음은 보안에서 가장 전방위로 많이 보이는 권한 제한 부분이다. 웹서버나 어플리케이션 권한을 ROOT 계정이 아닌 적절한 권한을 부여한 계정으로 돌린다든지(비교하자면 전쟁시 쪼랩인 중위에게 핵미사일 장치를 맡기지 않아야 하는 것과 비슷하다), 익명 FTP 를 제공하지 않는다든지(2.2 처럼 필요 없으면 FTP를 아예 설치 안하는게 더 바람직하다),  공용계정을 쓰지 않는다든지(누가 사고 쳤는지 알수 없거나, 어렵게 된다), 필요없이 자세한 로그를 남겨서 아무나 보게 한다든지(개인정보나 사용자의 주요 정보가 익명으로 노출될 수 있다), 어플리케이션에게 필요한 권한만 부여된 DB 계정을 발급한다든지, 루트권한을 안준다든지, everyone 권한의 폴더를 공유한다 든지 이다. 밑에 웹에서 찾은 예제들이 있다.

 

[리눅스에서 톰캣 일반 계정으로 실행하기 - 기록 > 기억님 블로그]

https://kimyhcj.tistory.com/75

 

[서버관리 - Anonymous FTP 비활성화 - IT 보물창고님 블로그]

https://skynarciss.tistory.com/125

 

[리눅스 루트계정을 항상 사용하면 보안에 문제가 되나요? - KLDP]

https://kldp.org/node/152000

 

[DB 접근제어 설계 - DBGuide.net]

http://www.dbguide.net/db.db?cmd=view&boardUid=152805&boardConfigUid=9&boardIdx=146&boardStep=1

 

[서버보안 가이드 - 폴더 권한 설정 - IT 보물창고님 블로그]

https://skynarciss.tistory.com/12

 

 

 

  이 부분은 사실 어플리케션 보안 측면도 있지만, 회사에서 많이 얘기되는 보안 정책 부분이기도 하다. 법적인 부분을 꼭 준수하고, 이외에는 사람들의 사용성 측면과 계속 딜을 해가면서 균형을 맞춰야 하는 부분 같다. 

 

2.4 그 외

  마지막으로 8교시 까지의 여러가지 측면(클라이언트 코드, 암호화, 인젝션, API, 업로드, 다운로드) 측면을 포함하는 포괄적인 영역이다. 해당 부분은 아래와 같이 기관에서 배포한 보안 가이드 문서(모바일, 일반 OS)를 살펴보면 될것 같다. 추가로 10교시에서 얘기할 설계 부분을 고려하여 문제에 접근 하면 어떨까 싶다. 

 

[소프트웨어 개발 보안 가이드]

http://www.kisa.or.kr/uploadfile/201702/201702140920275581.pdf

 

  사실 이 글 시리즈에서 목표하던 것 하나가 저런 보안 가이드를 만들때 왜 이런 항목을 구성해서 보안가이드를 만들었는지를 객관적으로 볼수 있도록 하고 싶었다. 뭐 얼마나 공감했을진 모르지만 일단은 1차 고지에는 도착한 기분이다.   

 

 

3. 대처 방법

  4. 그외 항목 이외에 위의 요소들의 주요 특징중 하나는 대부분은 좀 과거의 히스토리적으로 모아진 취약점의 경향이 강하고, 정형화되 있다는 것이다. 또한 수동 확인의 경우에도 특정 명령어를 날려 확인 한다든가, 화면을 보면 된다(경험상 화면을 봐서 할수 있는 것은 스크립트 등으로 자동화가 가능하다).

 

  결국 반복적으로 자동화된 체크를 수행하기 용이하다는 것인데, 이러한 자동화가 집적화된 결과가 여러 보안 스캐너나, 솔루션 들이다. 단순한 작업을 지치도록 수동으로 하기 보다는 적절한 ROI 를 따져서 무료나 유료, 또는 파이썬 등의 여러 언어로 만들어진 프로그램을 만들거나 코드를 구해 체크하면 효율이 나는 부분 같다. 이렇게 아낀 시간을 설계적인 측면이나, 코드 리뷰 등 자동화된 측면으로는 조금 한계가 있는 부분에 투자하는게 좀더 효율적이지 않을까 싶다. 물론 해당 부분을 자동화 하는 것을 스스로 한다면 많은 시행착오와 시간이 투여되는 부분일 수 있긴 한듯 하다.

 

 

 

4. 마무리 하면서

  다른 블로그들을 링크하여, 구렁이 담넘어가듯 써내려간 느낌이지만, 굳이 책이나 블로그에 잘 설명되어 있는 부분을 반복하는건 읽는 사람이나 쓰는 사람 모두 손해라고 생각해서 라고 생각해 줌 좋겠다. 설정 측면에서의 하드닝은 일반적인 어플리케이션 보안 관점보다는 노력이 덜 들고 자동화 할 수 있는 요소가 많은 부분이긴 하지만, 수동으로 할 경우 시간이 꽤 걸리고, 휴먼 에러도 일어날 수 있는 부분이다. 또한 실제 노출됬을경우 생각보다 큰 이펙을 주게 되는 요소라고 본다, 해당 부분은 단발적인 부분이 아니라 보안 쪽의 종합적인 정책과도 연관되어 결정되야 되는 측면도 있어 보인다. 단순해 보이긴 하지만 여러모로 균형을 잘 맞추면서 접근해야할 부분 같다.

 

 

 

2019.5.5 by 자유로운설탕
cs

 

 

 

posted by 자유로운설탕
2019. 3. 24. 21:38 보안

  API 는 사실 보안적으로 봤을때(사실 프로그램적으로 봤을 때도 비슷한 상황인거 같지만) 일반적인 다른 프로그램 요소와 특별히 다르진 않다. 다만 일반적으로 웹에서는 AJAX 와 연관되어 돌아가기 때문에 피들러 같은 특수한 툴로 살펴보지 않는 이상 호출이 된다는 부분조차 인지하지 못할 수 있어서 개발자의 경험이 없으면 체크를 놓치는 요소라고 보는게 더 맞을 거 같다. 어떤 측면에서는 일반적인 어플리케이션의 인터페이스보다는 표준화(보통 XML 이나 JSON 으로)가 많이 된 요소이기 때문에, 더 투명하게 검증 할 수 있는 측면도 있어 보인다.

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계 문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

 

1. 들어가면서

  그럼 API 는 무엇일까? 사실 이 얘기는 파이썬 글 10교시 Whois API 편을 보면 이미 필요한 부분에 대해서 얘기한듯 싶다. 샘플도 API 가  무언지를 보여주는 토큰 까지 있는 좋은 샘플이라고 생각하기 때문에 먼저 해당 글을 보고 이어서 보길 바란다. 여기서는 해당 글을 보고 왔다고 생각하고 얘기를 이어간다.

 

 

  거기에서 나왔던 얘기같이 API는 머리 꼬리를 띈 생선의 몸통 같이 데이터만이 왔다 갔다하는 프로그램이다. 사실 이 API는 소프트웨어의 시작 부터 계속해서 있어 왔다. API 의 약자 자체가 Application Programming Interface 이기 때문에, 어플리케이션에서 무언가를 호출해서(예전엔 주로 Windows API 등 OS 에서 제공하는 함수들-선을 그린다던가, 창을 띄운다던가, 화면에 글자를 뿌린다던가... 이였지만), 원하는 일을 하거나, 결과 값을 받아오는 형식을 말한다.

 

 

  인터페이스는 두 개의 다른 존재가 연결되는 방식을 얘기한다고 볼수 있는데, 우리 현실과 연계를 지으면 쉽게 비슷한 부분이 많다. 예를 들어 우리는 여러가지 인터페이스를 통해 타인 또는 타 물체와 소통 한다. 편의점에 가면 물건을 사기위해서 카드나, 현금을 주고 포스기로 결제한 후 물건을 담아오게 되고, 친구를 만나면 대화를 나누거나, 음식을 먹거나, 술을 먹거나 하면서 우정을 나누고, 은행에 가면 은행에 정해진 규칙에 따라서 상담원과 은행 업무를 본다.

 

  우리가 아이폰이나 안드로이드 폰을 사용하는 것도 폰의 인터페이스(터치, 드래그 및 여러 메뉴에 의한 사용자 인터페이스)를 이용한다고 보면 된다. 앞의 Whois API 예제에서도 해당 사이트에서 설계한 인터페이스 대로 데이터를 보내고 받아야 하는 것을 볼 수 있다. 우리가 생각하는 예의란 부분도 마찬가지여서, 예의에 대한 가치관이 다른(인터페이스가 다른) 두 사람이 만나면 관계가 엉망이 되어버리기도 한다. 

 

  프로그램 언어를 배우는 초입 부분에서 API 와 비슷한 부분을 볼수 있게 되는데, 해당 부분이 바로 함수나 메서드이다. 함수를 보면 정해진 타입의 입력 값을 넣어서, 정해진 타입의 출력 값을 얻게 된다. 파이썬의 모듈도 마찬가지로 해당 모듈에 정해진 방식대로 사용해야 한다. 어떻게 보면 프로그래밍 영역 자체가 수많은 인터페이스들간의 커뮤니케이션으로 이루어져 졌다고 봐도 될것 같다.

 

  

  추가로 API 에는 하나의 더 중요한 부분이 있는데, 호출 권한에 대한 인증 이다. 해당 부분은 3가지  정도의 측면이 있는데, 성능 및 데이터의 중요도, 모니터링 이다.

 

  첫째로 아무리 성능이 좋은 서버가 지원하는 상태라도 많은 사람들이 익명으로 계속 호출하게 된다면 병목 문제가 생길 수 있다. 이 부분은 크롤링 툴 등에 의해 페이지를 자주 호출 하는 경우에도 비슷한 문제가 생길수 있지만 API 는 좀더 특정한 목적을 가지고 내부에서 조회한 데이터를 전달하는 목적을 가지고 있기 때문에 좀더 민감하지 않을까 싶다. 

 

  둘째는 데이터의 중요도 인데, 특정 개인에게만 전달되야 되는 데이터가 다른 사람에서 전달되거나, 특정 권한을 가진 경우에만 전달 가능한 데이터가 아무에게나 보내지는 것은 바람직하지 못할 것이다.

 

  세번째는 모니터링 및 통제다. 현재 데이터를 가져가는 주체가 누구인지 특정할 수 있다면, 데이터가 어디로 나가고 있는지(호출하는 서버나 IP 정보만으로는 불충분하다), 얼만큼 데이터를 제공해야 하는지를 컨트롤 할수 없다.

 

 

 

 

2. 간단한 API 만들어 보기

  앞의 Whois API 를 봄으로서 API 를 사용하는 측면은 살펴봤으니 이번엔 한번 예제를 만들어 보자. 파이썬의 flask 모듈을 이용해 API 를 호출해 데이터를 가져오는 기능을 만들어 보려고 한다. 밑의 글들과 같은 REST API 샘플을 만들수도 있겠지만, 원리를 설명하기에는 간단한 쪽이 좀더 날거 같아서, 앞의 AJAX 예제와 비슷하게 만들었다.

 

[파이썬 Flask 로 간단한 REST API 작성하기 - readbetweentheline 님 블로그]

https://medium.com/@feedbots/python-flask-%EB%A1%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-rest-api-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0-60a29a9ebd8c

 

[Designing a RESTful API using flask restful - miguelgrinberg.com]

https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful

 

 

2.1 간단한 API 호출 예제

  우선 파이썬 코드 쪽을 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask import Flask, render_template, request, jsonify
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/call_api", methods=['GET'])
def test_search():
    return render_template('call_api.html')
 
 
@app.route("/getSecret", methods=['GET'])
def getSecret():
    name = request.args.get('myname')
    secret = name + "'s secret is A" 
    return jsonify({'var1': secret})
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

[flask_api.py]

 

  간단히 코드를 보면 call_api 가 기본 페이지이고, getSecret 가 API 역할을 한다. 해당 API 는 인자로 myname 을 받아서 해당 이름의 비밀을 알려준다(원래는 회원이 맞는지, 해당 회원의 비밀이 무언지를 아마 어딘가에 조회해야 할테지만 했다고 친다...). UTF-8 인코딩으로 c:\python\code 폴더에 flask_api.py 라고 저장하자.

 

 

  다음으로 템플릿 파일을 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<html>
  <head>
    <script src="http://code.jquery.com/jquery-3.3.1.min.js" ></script>
      <title>Call API</title>
      
    <script type="text/javascript">
      $(document).ready(function(){
 
        $("[id^=checkData]").click(function() {
          var myname = "tom"
           
          $.ajax({
            url: "/getSecret",
            data: { myname : myname },
            contentType: 'application/json;charset=UTF-8',
            success: function(data){
              $("#spanName").html(data.var1);
            }
          });
        });
        
      });
 
    </script>
  </head>
  <body>
    <table>
      <tr>        
        <td><input type="button" id="checkData" value="체크"></td>
      </tr>
      <tr>               
        <td><span id="spanName"></span></td>
      <tr>
    </table>
  </body>
</html>
cs

[call_api.html]

 

  내용을 보면 "checkData" 라는 버튼이 하나 있고, 해당 버튼을 누르게 되면 AJAX 기능을 이용해서 getSecret API 에 myname 을 "tom" 이라는 GET 인자를 포함해서 호출 한다. 역시 UTF-8 인코딩으로 c:\python\code\templates 폴더에 call_api.html 이란 이름으로 저장한다.

 

 

  아래와 같이 플라스크 사이트를 실행 후 주소창에, http://localhost:5000/call_api 라고 입력 후, "체크" 버튼을 클릭한다. 밑과 같이 tom 의 비밀이 API 를 통해 전달되어 나오게 된다(이해가 안되는 경우는 앞에 소개한 피들러로 함 살펴봐도 좋다)

c:\Python\code>python flask_api.py

 

 

  그럼 위의 getSecret API 가 가질 수 있는 문제점은 무엇일까? 앞에서 얘기했듯이 아무나 해당 API 를 호출하면 문제가 생길 수 있다. 웹에서는 쿠키에 저장된 암호화된 인증 값을 이용해서 신원을 확인 하는데, API 의 경우는 HTTP 방식으로 ID/PASS 를 통해 인증할수도 있지만, 보통 호출하는 주체가 자동화된 프로그램일 경우가 많기 때문에 일일히 패스워드를 입력하여 넘기기는 어렵다(물론 어딘가에 저장해 두었다 프로그램이 넘겨도 되긴 한다). 그래서 좀더 자주 쓰이는 방식이 토큰(Token)을 사용하는 것이라고 본다(해당 토큰 개념은 스크립트 문제에서 얘기한 CSRF 의 방어에도 쓰인다).

 

 

 

2.2 토큰 추가 예제

  그럼 간단한 토큰을 사용하는 예제를 만들어 보자. 똑같이 플라스크 쪽 코드 부터 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask, render_template, request, jsonify
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/call_api_token", methods=['GET'])
def test_search():
    return render_template('call_api_token.html')
 
 
@app.route("/getSecret", methods=['GET'])
def getSecret():
    name = request.args.get('myname')
    token = request.args.get('mytoken')
    if token == "@$ABC77":
       secret = name + "'s secret is A"
    else:
       secret = "Need valid token!" 
    return jsonify({'var1': secret})
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

[flask_api_token.py]

 

  내용을 보게되면 앞의 코드와 거의 비슷하지만, mytoken 이라는 사용자가 보낸 토큰을 확인하여, "@$ABC77" 값이 아니라면 "Need valid token!"라고 에러메시지를 대신 보낸다. UTF-8 인코딩으로 c:\python\code 폴더에 flask_api_token.py 라고 저장하자.

 

 

  마찬가지로 템플릿 코드 쪽을 보면, API 를 호출하는 인자에 mytoken 값이 추가됬다. 일부러 토큰은 앞에 "X" 가 추가로 들어가 값이 틀리게 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<html>
  <head>
    <script src="http://code.jquery.com/jquery-3.3.1.min.js" ></script>
      <title>Call API</title>
      
    <script type="text/javascript">
      $(document).ready(function(){
 
        $("[id^=checkData]").click(function() {
          var myname = "tom"
          var mytoken = "X@$ABC77"
           
          $.ajax({
            url: "/getSecret",
            data: { myname : myname },
            contentType: 'application/json;charset=UTF-8',
            success: function(data){
              $("#spanName").html(data.var1);
            }
          });
        });
        
      });
 
    </script>
  </head>
  <body>
    <table>
      <tr>        
        <td><input type="button" id="checkData" value="체크"></td>
      </tr>
      <tr>               
        <td><span id="spanName"></span></td>
      <tr>
    </table>
  </body>
</html>
 
cs

[call_api_token.html]

 

  UTF-8 인코딩으로 c:\python\code\templates 폴더에 call_api.html 이란 이름으로 저장한다.

 

  아래와 같이 플라스크 서버를 실행 후 주소창에, http://localhost:5000/call_api 라고 입력 후, "체크" 버튼을 클릭한다. 의도했던 대로 토큰 값이 틀리다고 에러가 난다.

c:\Python\code>python flask_api.py

 

 

 

3. 어플리케이션 보안 측면의 API

  그럼 어플리케이션 보안 측면에서 API 를 어떻게 봐야하는지를 생각해 보자. 

 

 

  첫번째로 앞의 예제에서 본 것처럼 요즘의 API 들은 사실 엄청 단순한 모양을 가지고 있다(약간 인자들이 암호같은 Windows API 등에 비해서 말이다--;). 사실 하는 일도 단순하다. 프로그램의 함수가 입력을 받거나 받지 않을 수도 있고, 출력을 주거나 주지 않을 수 있듯이 API 도 마찬가지 이다.

 

  입력을 받아 단순히 로그로 저장할 수도 있고, 구입 목록 같이 입력에 대한 특정한 정보를 줄수도 있고, 빌드나 다른 API 의 호출 등의 특정한 액션을 할수도 있다. 그 부분은 API 가 어떤 기능을 하느냐에 따라서 달라지는 일이라서 블랙박스적으로 보면된다고 생각될수도 있지만, 보안적으로 봤을때는 한가지 중요한 점이 있다. 그것은 API 의 입력으로 들어가는 값들이 API 내부의 로직에 영향을 미칠수 있다는 것이다.

 

  이렇게되면 앞에서 얘기했던 클라이언트 코드와 인젝션에 대한 이야기로 다시 주제가 돌아가게 된다. API 가 사용하는 인자 중 나쁜 영향을 미칠수 있는 인자를 파악하는 방법은 API 내부의 동작을 이해해야하는 문제가 되버린다. 

 

 

  두번째는 앞의 예제에서 봤던 토큰이다. 이 토큰이 변조되거나, 바꿔치기 되는 문제에 대해서 웹에서의 세션관리와 동일한 종류의 문제가 발생되게 된다. 추가로 어떻게 토큰을 최초 사용자에게 발급하고, 토큰으로 인한 자격의 유지 기간에 대해서도 고민해 봐야한다. 프로그램이 주로 호출하는 API 의 특성상 보통 한번 발급한 토큰을 특별한 제약없이 영구히 사용하는 경우도 많은듯 하지만, 관리의 실수로 토큰이 노출되었을 경우 골치아픈 문제가 발생할수 있으므로 여러 측면에서 리스크를 검토해야 한다(마치 암호화페에서 열쇠에 해당하는 개인키가 도난당하는 것 같은 일이 생길 수도 있다).

 

 

  세번째로 전송되는 데이터를 평문으로 보내도 될까 하는 부분이다. HTTPS 로 감싸 보내는 방법도 있겠지만 해당 부분은 보내는 쪽의 의도적인 조작이나 중간자 공격에 100% 안전하지는 않을 것이기 때문에, 아예 공개키/개인키 등을 사용해서 암호화 하여 보내는게 맞을 수도 있다. 해당 부분은 법적인 부분과, 데이터의 중요성, 여러 위험 요소를 고려해서 선택해야하는 부분인것 같다.

 

 

  마지막으로 OWASP 에서도 중요하게 강조하고 있는 모니터링 부분이다. 중요한 API 일수록 해당 토큰을 발급한 쪽에서 필요한 만큼 적절하게 API 를 호출 하는지를 체크해야 하는 경우가 있게된다. 해당 정보가 법적으로 보호되어야 하는 민감한 정보라면 더더욱 그렇다. 해당 부분은 어플리케이션 쪽에서 토큰 및 여러 클라이언트 쪽 정보를 기반으로 기준을 정해 모니터링을 해야되는 문제이다.

 

 

 

4. 마무리 하면서

  조금 싱거운 감이 있지만 개인적으로 API 는 일반 어플리케이션과 별로 다르게 볼 필요는 없다고 생각하기 때문에 여기서 마무리를 하려한다. API 는 전송하는 데이터와 토큰이라는 관점에서 보게 되면, 일반적인 어플리케이션과 특별히 다르지는 않다. 다만 해당 형태의 구조를 명확히 이해하지 못한다면 미지의 영역이 되어 전혀 다른 것처럼 보일 수가 있다. 

 

  요즘처럼 API 로 조각조각 나누어진 어플리케이션을 테스트 하는것은, 인터페이스들이 늘어나는 결과를 가져오기 때문에 테스트를 해야되는 입장으로는 꽤 귀찮은 일이긴 하다. 2교시에서 얘기했던 것처럼 소스 레벨의 관점에서 문제를 바라보는 것도 나쁘진 않을 거라고 생각한다.

 

 

 

2019.3.31 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
prev 1 2 3 next