서론

다른 회사도 다 그러는진 모르겠는데,, 이 회사에선 회사에서 진행하는 업무들을 '프로젝트' 로 구분하고, 프로젝트별로 매출/비용 등을 인지한다.
이를 위해선 하나의 법인 안에 합쳐져있는 예금, 카드, 세금계산서/계산서(이하 편의상 그냥 세금계산서.) 등에 대해 장부를 작성하면서 또한 각각 어떤 프로젝트에 속한지도 기록이 필요하다.

원랜 재무 직원이 진행을 했는데,, 회사가 커지고 프로젝트가 많아지니 감당이 안되어서, 각 부서별로 일단 세금계산서에 대해서만이라도 선처리를 해달라고 요청을 하게 되었다.

요청 내용은 대략 아래와 같다.
1. 세금계산서 목록을 홈택스에서 엑셀로 내려받는다
2. 엑셀에서 내 부서에 해당하는 항목들을 찾아 필터링 한다
3. 장부 작성용 계정과목도 분류한다. (eg, 가스수도비, 용역비, ...)
4. 회계 프로그램에 각 내용을 하나씩 입력한다. (전표 생성)

위 업무 요청을 위해 재무에서 만든 가이드 중 일부

홈택스와 엑셀과 회계 프로그램을 오가야하고, 없던 일이 생기는데다가 계정이니 뭐니 회계 용어도 알아야하고,,
게다가 본사 밖에선 회계 프로그램 이용을 위해서 VPN 까지 사용해야하니,,
그냥 포기해버리더라....ㅋㅋㅋㅋ

재무에선 점점 많아지는 일에 깔려 죽을라하고,, 각 부서에선 교육한다고 불러도 모르겠다고 멍 때리고 있고...
이렇게 삽질하고 있는걸 우연히 발견해서 해결해보기로 했다.

시스템 배경

이 회사에서 그룹웨어는 내가 만든 시스템을 사용하고 있는데, 그 기능 중 하나로 전자결재가 있고, 전자결재 이용 편의를 위해 홈택스 계산서 스크래핑 및 이를 첨부하는 기능이 있다.
단순히 가져와서 표시에 추가로 검색이나 수정 등 연관 계산서의 간편한 조회, 전자결재에 사용되었는지 여부 표시 등 이런저런 편의 기능이 들어가 있다. (좀만 다듬으면 다른 회사에도 팔아먹을 수 있지 않을까 생각했는데,, 팔기 위한 서비스 개발은 늘 어렵다ㅠㅜ)

해당 그룹웨어에 내가 구현해둔 세금계산서 조회 기능. 스크래핑도 직접 구현한 기능에 의존하다 보니 드물게 막힐 때가 있다ㅠ

아무튼 세금계산서 기능은 이미 있으니, 여기서 각 세금계산서에 대해 [프로젝트 설정, 과세 구분, 계정과목 설정] 기능을 지원해주고,
이렇게 입력된 정보를 [회계 프로그램에 밀어 넣어주기] 를 하는게 이번 프로젝트의 완성⭐️

그리고 블로그에서 이 카테고리의 취지가 그렇듯, 이 글을 쓰게 된 이유는 [회계 프로그램에 밀어 넣어주기] 와 관련되어 있다.

[프로젝트 설정, 과세 구분, 계정과목 설정]를 위한 프로젝트별 계정 과목 제약, 일괄 처리 등은 그냥 웹서비스로 만들면 되는거니까 대충 적당히 만들었다. 실무자들 불러다가 교육하고 피드백 정도가 남은 상황.

 

회계 프로그램

이 회사는 더X 사의 아이큐X 라는 시스템을 이용 중이다. (이름을 숨길 생각은 없지만 그렇다고 검색으로 떠서 좋을 것도 없으니 적당히 마스킹)

사내 PC 한대에 서버 프로그램 및 SQL Server 를 깔아두고, 각 작업자 PC 에선 이 서버로 접속해서 사용하는 구조이다.
최초 도입비를 좀 비싸게 받고, 이후엔 유지보수 비용으로 약간씩 받으며, 카드 등 스크래핑 모듈 정도는 주기적으로 업데이트 해주는 듯?

이전 지문 인식 소프트웨어가 그렇듯, 로컬에 설치된 서비스니까 접속 정보는 어떻게든 획득할 수 있다. 아니 그 이전에 그냥 인터넷에 공유가 되어 있더라.
다른 일로 이 회사 영업사원과 통화할 일이 있었는데, DB 직접 접속으로 활용하는건 다른 곳에서도 자주 있는 것 같더라. 회사 차원에서 반기진 않지만 그렇다고 막을 순 없으니 어쩔 수 없다,, 정도의 느낌? 차세대 서비스는 이런 문제가 좀 더 방어가 되어 있다고 하는데,,,

아무튼 이 서비스는 서버 PC에 대한 방화벽을 제대로 설정해두지 않으면, 비밀번호도 공개되어 있겠다 권한이 없는 직원도 데이터를 훔쳐갈 수 있는 상당히 취약한 구조이다. 물론 후술할 복잡한 작업을 다 했다는 가정하에. 현실적으론 일어나기 힘들 것 같긴 하다.
그래서 개인적으론 꽁꽁 막아두는 것 보단 이렇게 적당히 열려있어서 내가 손을 댈 수 있는 여지가 있는 환경을 선호한다 ㅎ
막아야 한다면 내가 막아도 되고...

우리에겐 DB스키마 정보가 없으니,, DB 에 접속하면 불친절한 테이블명과 컬럼명들을 한가득 마주하게 된다.

사용량으로 테이블을 선별하고, 이름을 추론하고 데이터와 비교하면서 나에게 필요한 정보를 하나씩 찾는 지루한 작업...

컬럼을 하나 찾으면, 아래와 같은 쿼리로 동일 컬럼이 있는 다른 테이블을 찾을 수도 있다. 이 생각을 못해서 처음엔 하나하나 한참을 헤맸었다ㅠ

SELECT
    t.name AS table_name,
    c.name AS column_name
FROM sys.tables AS t
JOIN sys.columns AS c ON c.object_id = t.object_id
WHERE c.name = N'찾으려는컬럼명' AND EXISTS (
      SELECT 1
      FROM sys.dm_db_partition_stats AS ps
      WHERE ps.object_id = t.object_id AND ps.reserved_page_count > 0
)

 

헤매다보니 대충 DB 내에서 현재 수집된 세금계산서, 등록된 전표, 전표의 승인 여부 등 필요한 데이터는 얼추 찾았다.
완벽한건 아니지만,, 필요한 정보만 어떻게든 가져오면 되고 당장 정확성이 엄청 중요하지 않기 때문에, 문제 발생시 수정해주면 되고..

 

데이터 입력

문제는 데이터 밀어넣기.
어떤 테이블과 어떤 컬럼에 정확한 값을 넣어주지 않으면 프로그램이 오작동을 할 수도 있다.

이번에도 프로그램의 작동 로직을 분석하면 되는데,, 지금은 결국 DB에 기록되는거니, SQL 쿼리만 잘 찾으면 된다.

MSSQL 에는 Extended Events 란게 있다. 활성화해두면 실행된 쿼리 등에 대한 정보에 대한 로그가 설정한 경로에 기록되고, 이 파일에 대해 다시 쿼리로 이벤트를 조회할 수 있다.

다만 이걸 활성화하려면 sysadmin 권한이 있는 계정이어야 하는데, 위에서 말한 기본 계정은 해당 권한이 없다.
어차피 로컬 시스템이니까, 어찌저찌 생성해주면 된다. 이 과정은 생략.

-- 이벤트 생성
CREATE EVENT SESSION [TempSqlLog] ON SERVER
ADD EVENT sqlserver.rpc_completed( -- 프로시저 실행
    ACTION(sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.database_name,  sqlserver.username)
),
ADD EVENT sqlserver.sql_batch_completed( -- SQL 쿼리 실행
    ACTION(sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.database_name,  sqlserver.username)
)
ADD TARGET package0.event_file(
    SET filename = N'C:\SqlLog\SqlLog.xel',
        max_file_size = 100,
        max_rollover_files = 10
);

-- 시작/종료
ALTER EVENT SESSION [ShoSqlLog] ON SERVER STATE = START;
ALTER EVENT SESSION [ShoSqlLog] ON SERVER STATE = STOP;

sql_statement_completed 란 이벤트도 있었는데 유의미한 데이터가 없었던 것 같아서 제외, 오래전에 한거라 기억이 잘 나진 않는다.

action 에서 client_app_name, database_name, username 은 큰 의미는 없지만, 그냥 넣어뒀다. client_hostname 정도는 분석 과정에서 다른 사용자의 실 사용이 혼동되니 필터를 위해 사용했고..

 

로그를 켜두고, 전표 등록을 하고, 로그를 끄고, 이벤트를 질의한다.

WITH x AS (
    SELECT CAST(event_data AS xml) AS xe FROM sys.fn_xe_file_target_read_file('C:\SqlLog\SqlLog*.xel', NULL, NULL, NULL)
)
SELECT
    xe.value('(event/@name)[1]', 'nvarchar(60)') AS event_name,
    DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), GETDATE()), xe.value('(event/@timestamp)[1]', 'datetime2(3)')) AS ts,
    COALESCE(
        xe.value('(event/data[@name="batch_text"]/value)[1]', 'nvarchar(max)'), -- sql_batch_completed
        xe.value('(event/data[@name="statement"]/value)[1]',  'nvarchar(max)')  -- rpc_completed (환경에 따라)
    ) AS sql_text
FROM x
WHERE
	xe.value('(event/action[@name="client_hostname"]/value)[1]', 'nvarchar(128)') = 'DESKTOP-....'
ORDER BY ts DESC;

 

이러면 실제 회계 프로그램이 실행하는 쿼리들을 확인할 수 있다.

내가 필요한 전표 입력엔 특정 프로시저를 호출하는걸 확인했다. 프로시저의 내부 쿼리는 암호화 저장되어 확인할 수 없었지만, 나도 똑같이 실행만 하면 되니까 프로시저의 내용물까진 필요없긴 하다.

이제 종류별로 전표 발행을 해보면서, 프로시저 호출시의 각 필드가 어떤 값인지 파악하고, 나도 동일하게 호출해주면 된다.


이렇게 데이터를 다루다보면 아래와 같은 재무제표들도 이것저것 만들어볼 수 있을 것 같긴 한데,,,
이건 아직 필요성을 못 느끼는 것 같기도 하고 나도 공부해야될게 많아져서 일단은 보류...

(아래 화면은 개발 중 시스템의 일부이긴 한데 값은 모두 AI 가 생성한 더미데이터이다.)

'프로젝트 모음' 카테고리의 다른 글

SUPREMA 지문 출입/근태기 연동  (0) 2026.09.21
전자책(EPUB) DRM 해제  (0) 2026.02.22
카테고리 프롤로그  (0) 2026.02.22

내가 몇몇 일을 봐주고 있는 회사 하나가 층별 출입 통제로 세콤 지문 단말을 사용하고 있었는데, 사옥을 리모델링하면서 8층은 엘리베이터에서 바로 접근이 가능해졌다.
그래서 엘리베이터에 8층 버튼은 그냥 눌러선 눌리지 않고, 지문 인증시에만 작동하도록 설치가 되어있었다.

그렇게 별 생각없이 살고 있었는데, 건물 관리 담당 직원을 보니 신규 직원 입퇴사시마다 건물 관리 보안용으로 세콤에 사용자 등록을 하고, 또 8층 엘리베이터 용으로 신규 직원들을 매번 모아 관리실에 가서 지문 등록을 또 따로 해주고 있더라.

지문 단말기는 Suprema 사의 BioStation L2 라는 장비로, 글 작성 시점 기준으론 지원 종료된 단말기이다.
관리실에는 Windows 서버, 엘리베이터에 장착된 L2 단말기, 등록용 L2 단말기와 이들을 연결하는 공유기로 구성되어 있다.

대충 이런 구조..

담당 직원이 매번 관리실로 가야하는 이 비효율과 퇴사 지원의 관리 문제, 그냥 시스템에 대한 관심이 생겨서, 개선할 방법이 없나 이래저래 살펴보았다.


서버 PC 에 어떻게 하는지 살펴보니, 웹브라우저로 관리 페이지에 접속해서 사용자 등록, 등록 과정에서 지문 스캔을 누르면 옆에 있는 등록용 단말기가 반응하고, 지문을 입력할 수 있는 구조였다.

서버 PC 에선 로컬에 MariaDB, java 등을 설치하고 웹 서버가 구동되고 있었고, 서버 내 DB 정보를 단말기 간에 동기화 해준다. 서버가 꺼져있어도 각 단말기는 직전 동기화 정보 기준으로 정상 작동함.

BioStar 2 라는 관리용 웹 콘솔과, 사용자 추가 화면

그리고 엘리베이터까지 뜯어보진 않았지만,, 단말기 뒷쪽을 보면 전원, LAN, 릴레이 신호 출력 등을 연결할 수 있는 웨이퍼 커넥터들이 달려있다.
웹 장치 설정을 보면, 특정 이벤트마다 어떤 동작이 수행될지 정의할 수 있는데, 카드 / 지문 / PIN 등 각각 인증 성공시 릴레이로 신호 출력을 걸어둘 수 있다. 릴레이 출력에 문 개폐 등의 장치를 연결해두는 식인 듯. 우리는 엘리베이터에 8층이 눌리도록 배선되어 있겠지?

또한 서버 PC 로컬에 설치된 MariaDB 엔 접속해보면 사용자 관련 데이터는 암호화되어 있어서 DB 직접 접근은 조금 제한되지만 이 서버는 감사하게도 API 를 제공한다.
단, 기본 웹서버는 로컬만 바인드하고 있기 때문에, BioStar 2 Setting 이라는 프로그램을 실행해서 Unified Gateway 를 실행해주면 nginx 를 통해 웹서버가 다시 구동된다.
내 기억엔 이 과정에서 전체 인터페이스로 바인드 했던 것으로 기억.. 아니라도 nginx 는 설정 파일 변경이 용이하니 설치 디렉토리에서 nginx 를 찾아 설정을 변경해주자.

이제 해당 PC 로의 접근이 가능하다면, BioStar X API 문서를 참고하여 시스템을 제어할 수 있다.
위의 BioStar 2라는, 제어용 웹사이트 자체도 이 API 를 기반으로 작동하는 듯?

API 가 완벽한건 아닌 것 같지만,,, 사용자 목록 조회, 사용자 수정(지문 추가 등)/비활성화/삭제, 기기로 지문 스캔 요청 등은 모두 가능하다.
사용자 관리야 그러려니하고, 지문 등록 기준으론 다음 과정을 거치면 된다. 한개의 지문(fingerprint)은 등록시 2개의 스캔 결과(template)가 필요하다.

  1. 지문 스캔 두번 - Scan Fingerprint
    • API 를 호출하면, 요청에서 선택한 기기로 지문 인식해달라는 메시지가 바로 표시된다.
    • 정상 스캔시 지문의 퀄리티가 기준치를 충족하는지 확인. Web 에서 기본값은 80인 듯, API 이용한다면 직접 검증 필요
  2. 이미 등록된 지문과 일치하는 template인지 확인 - Verify Duplicate Fingerprint
  3. 스캔한 2개의 지문(template)이 동일한 지문이 맞는지 검증 - Verify Fingerprint Scan
  4. 사용자 정보에 해당 지문(2개의 template 쌍) 추가하여 수정 - User: Update
    • fingerprint_templates 에 기존값 포함하여 추가

 

이제 제어는 되었고, 매번 관리실로 내려가는 수고를 덜기 위해, 등록용 단말기를 사무실로 가지고 올라와야한다.
서버 PC 와 같은 망에 있어야 하니, 사무실과 관리실까지 건물 내부 EPS 실에서 랜선을 내리니 마니 쌩쑈를 하다가,,

관리실의 서버PC 로 공유기에서 포트포워딩을 해두고, 장비에서 서버의 주소를 공인 IP 로 설정해버렸다.

‘장치에서 서버 연결’이 활성화되어있으면, 단말기에서 서버주소:포트/tcp 로 연결을 요청하고, 연결이 끊겼을 땐 10초? 30초? 잘 기억 안나는데 그 정도 텀을 두고 서버 연결을 시도한다.
장치 포트는 어디 쓰는지 모르겠고 서버 포트 하나만 사용함. 아마 장치에서 서버 연결을 끄면 쓰일 수도 있을 것 같은데, 굳이 테스트해보진 않았다.
연결 성공시 30초 단위로 ping 을 보내면서 연결을 유지하고, 서버에서 지문 인증 요청 등 요청을 보내면 이 채널을 통해서 단말로 명령을 보내고 단말이 작동하는 구조.

등록용 단말기를 테스트한다고 집으로 가져왔는데, 여기서도 잘 작동하는 것을 확인…ㅎㅎㅎ
이제 관리실에 내려가지 않고도 지문 등록이 가능해졌다. 다만 서버 컴퓨터가 항상 켜져있어야 한다는 제약이 생김..


이대로만 끝냈으면 글을 쓰지도 않았을 것,, 시스템 자체에 관심이 생겨서 좀 더 살펴봤다.

DB 암·복호화

로컬에 설치된 DB 는 어찌저찌 접속해도, 사용자 정보에 관한 부분은 대부분 암호화되어 있다.

사실 API 로 가져오면 되니까 크게 필요는 없기도 하다.
이 DB를 그대로 쓴다면, 기기 동기화 시켜야하니 API 호출이 어차피 필요하고, BioStar 서버 자체를 안쓰겠다면 DB 암호화를 깰 필요도 없으니.. 그래도 일단 작업해본 김에 간단하게 기록삼아.

눈으로 보이다시피, &@~ 로 시작하면 암호화된 데이터이고, AES + PKCS7 패딩으로 이루어져있다.
BioStar 설치 폴더/util/enckey 에 BioStar.secure_communication.encryption_key 로 암호화를 하는데, 이 키를 그대로 쓰진 않고 여기다가 xor 및 시저 암호를 간단히 적용하여 key, iv 를 만들어낸다.
이 후 각 record 를 복호화하면 됨.

 

프로토콜 분석

Suprema 는 위에서 내가 사용한, BioStar 2 서버를 사용하지 않고도 단말기를 제어할 수 있게 Device SDK 란걸 제공한다.
관련 문서는 BioStar Device SDK 에서 확인할 수 있고, 샘플 코드와 Windows/Linux 각 32bit/64bit 라이브러리를 제공한다. (ARM은 없지만…)

위에서 얘기한, ‘장치에서 서버 연결’ 를 설정해두면 기기에서 서버의 tcp 포트로 소켓을 연결한다. 초기 키 교환 이후 요청은 AES로 암호화하여 주고받는다.

  1. 서버는 연결되자마자 32B source_key 를 고정 키로 암호화하여 응답 반환
  2. 장비는 source_key 를 해싱하여 key, iv 생성, 이 후 상호 통신 간엔 AES-256-CBC 로 암호화하여 통신
    • 평문 20B + 암호화 데이터{ 헤더 16B + Payload }

SDK 에 통신 프로토콜에 대한 구조는 없어서, SDK 없이 직접 구현은 좀 까다롭다.

그래도 최소 기능만 사용 기준으로 어찌저찌 거의 대부분 다 만든 것 같은데…
방금 새로 스캔한 지문이 이미 누군가에게 등록된 지문인지 확인할 방법이 없다ㅠ
이 기능 하나가 없어서 기껏 만든게 모두 도로묵...ㅠㅠㅠ

 

지문 template 분석

지문 검사를 내가 직접해줄 순 없을까? 싶기도 하고 이 쯤 오니 궁금해져서, 지문 template 데이터도 분석해봤다.
template은 지문 스캔 이미지를 일부 전처리한 후 융선과 특징점을 추출한 데이터로, 최대 384B 의 데이터를 base64 인코딩되어 있다.

설정상 ISO 나 ANSI378 이란 포맷도 있던데,, 현재 기본 값은 Suprema 포맷.

지문 이미지가 최대 288x320 로 만드는 듯 하고, 한 셀이 16px, 최대 18x20의 융선과 특징점으로 표현하고 있다. 세로는 20셀 고정에 가로는 14,16,18 등등 가변으로 사용하는 듯?

지문 원본 이미지는 회색 음영인 PGM 포맷이고, 설정에서 지문 - 이미지 표시 활성화시 단말 기기에서 바로 볼 수 있는 이미지이다.
이 raw 이미지는 스캔시 받을 수 있도록 API 구조에 있긴 한데, 실제로 이미지를 주진 않았던 같다. (이런식으로 API 가 뭔가 구멍이 하나씩 빠져있다..ㅠ) 난 SDK 구현 이용해서 가져옴.

웹이나 API 사용시 기본적으로 template 을 이미지로 변환한, 두번째 형태의 이미지를 준다.

template 구조를 분석하면 3번쨰 이미지를 자체적으로 생성할 수도 있음. 세부 구조 분석 결과는 생략.


분석한 구조를 바탕으로 자체 지문 매칭을 구현할 수 있을까,, 도 좀 고민해봤는데 쉽지도 않고 실제 작동과 차이도 발생할 것 같아서 일단 말았다ㅠㅜ
기껏 시간 투자했는데, 그냥 간단했던 API 방식으로 회귀하는 것으로...ㅠㅠㅠ

아직 만들진 않았는데, 자체 ERP 에 지문 등록 기능을 만들어두고 각자 직원이 스스로 직원 등록을 하게 진행할 예정이다.
보안 문제가 살짝 있을 순 있겠지만,, 이게 근태나 엄격한 시설 출입에 사용되는 것도 아니라서, 일단 괜찮을 듯?

Odroid 공홈에서 판매중인 와이파이 모듈
공홈에서 CF-813B 의 OEM이며, Realtek RTL8821CU chipset 을 사용한다고 나와있다.

공홈에서 파는거니 확실히 지원이 보장되겠지만,, 크기가 조금 크기도 하고 결정적으로..

처음 꽂은 후로 뺄때 분리가 되어버렸다.......ㅠㅠㅠㅠ

조금 큰 본체가 마음에 안들기도 해서 대체품을 찾아봤는데,
무선랜이 은근히 드라이버를 많이 가린데서 조금 고민이 되었다.

다른 애들도 작동을 안하진 않을 것 같지만,, 일단 가능한 동일 칩셋으로 찾아본 제품이 NEXT-653WBT 이거.

블루투스 등 스펙도 동일하고, 연결시 드라이버도 동일하게 잡히고 잘 작동한다.
최저가 기준 가격도 조금 더 저렴하고,, 크기도 작고..!!

Ubuntu 24 + Odroid N2+ 에서 테스트해봤고,
Ubuntu 24 + NanoPi RS3 에서도 별다른 드라이버 과정없이 연결하자마자 작동하는 것 확인.

Ubuntu 24 + Odroid N2+ 기준, 위 2개 상품은 8821c 칩셋이고, 이 드라이버가 커널 내장이라 정상 작동한다.
iptime A3000MINI 의 경우 8822b 이고, 해당 드라이버가 Odroid 공식 배포 Ubuntu 24 배포판에 들어 있긴 한데,,,
zstd 압축이 되어 있는데, 커널이 압축된 드라이버 옵션이 빠진 채로 빌드되어 있어 로드되지 않는다.

테스트는 안해봤지만,, 압축만 해제해주고 재부팅하면 될 것 같음

# 8821cu 는 커널 내장이라 안해줘도 됨
# unzstd --keep /lib/firmware/rtw88/rtw8821c_fw.bin.zst
# 8822b
unzstd --keep /lib/firmware/rtw88/rtw8822b_fw.bin.zst

+ Recent posts