Visual Studio 2022 환경에서 레거시 Windows 버전과의 호환성을 유지하면서 암호화 기능이 탑재된 경량 SQLite 데이터베이스 애플리케이션을 구축하는 방법을 다룹니다. 최종 결과물은 외부 의존성 없이 단일 실행파일로 배포 가능합니다.
프로젝트 구성 전략
멀티바이트 문자 기반의 경량 실행파일을 얻기 위해 빌드 체인을 단계적으로 구성합니다. VC6.0의 프로젝트 구조를 기반으로 하여 바이트 단위 문자 처리를 유지하고, 중간 변환 도구를 통해 최신 IDE와 연결합니다.
- VC6.0에서 Win32 프로젝트 템플릿으로 기본 구조 생성 (기본 멀티바이트 설정)
- VS2010에서 프로젝트 형식 업그레이드
- VS2022에서 열기 — SDK 및 플랫폼 툴셋 변경 없이 로드
wxSQLite3 통합
암호화 기능을 제공하는 wxSQLite3 라이브러리의 특정 버전(4.5.1)을 사용합니다. 상위 버전은 디렉터리 구조가 변경되어 이 방식과 다를 수 있습니다.
압축 해제 후 wxsqlite3-4.5.1/sqlite3secure/src 경로의 전체 내용을 프로젝트 작업 폴더로 복사합니다. 프로젝트 속성의 VC++ 디렉터리 설정에서 포함 경로와 소스 경로 모두에 해당 위치를 추가합니다.
솔루션 탐색기에서 sqlite3.h와 sqlite3secure.c를 프로젝트에 포함시키고, sqlite3secure.c 파일 속성에서 미리 컴파일된 헤더 사용을 비활성화합니다.
컴파일 플래그 구성
프로젝트 속성 → C/C++ → 전처리기 정의에 다음 매크로를 추가합니다:
SQLITE_ENABLE_RTREE
SQLITE_ENABLE_COLUMN_METADATA
SQLITE_HAS_CODEC=1
SQLITE3ENCRYPT_EXPORTS
SQLITE_ENABLE_FTS3
SQLITE_ENABLE_FTS3_PARENTHESIS
SQLITE_SECURE_DELETE
SQLITE_SOUNDEX
CODEC_TYPE=CODEC_TYPE_AES256
Rebar 컨트롤 등 레거시 Win32 UI 요소를 사용하는 경우, stdafx.h에 최소 Windows 버전을 명시합니다:
#define WINVER 0x0500
#define _WIN32_WINNT 0x0500
암호화 API 활용
표준 SQLite와의 차이점은 데이터베이스 연결 후 키 설정 단계입니다. 최초 실행 시 비밀번호가 설정되며, 이후 접근 시 동일한 키로 복호화됩니다.
int status = sqlite3_open(dbPath, &handle);
if (status == SQLITE_OK) {
// 키 길이는 마지막 인자로 명시 (바이트 단위)
status = sqlite3_key(handle, "mysecret", 8);
}
비밀번호 제거가 필요한 경우 sqlite3_rekey(handle, NULL, 0)를 호출합니다.
래퍼 클래스 구현
Win32 API 스타일의 C++ 퍼를 작성하여 데이터베이스 작업을 캡슐화합니다. 리소스 관리를 RAII 패턴으로 처리합니다.
// SecureDB.h
#ifndef SECURE_DB_H
#define SECURE_DB_H
#include "sqlite3.h"
class SecureDB
{
public:
SecureDB();
~SecureDB();
bool Initialize(const char* path, const char* key);
void Terminate();
bool Execute(const char* statement);
bool Fetch(const char* query, int& rows, int& cols);
char** GetResults() const { return resultGrid; }
static int Callback(void* context, int count,
char** values, char** names);
private:
sqlite3* connection;
char** resultGrid;
bool active;
};
#endif
// SecureDB.cpp
#include "stdafx.h"
#include "SecureDB.h"
SecureDB::SecureDB()
: connection(nullptr), resultGrid(nullptr), active(false)
{
}
SecureDB::~SecureDB()
{
Terminate();
}
bool SecureDB::Initialize(const char* path, const char* key)
{
if (connection) {
sqlite3_close(connection);
}
int rc = sqlite3_open(path, &connection);
if (rc != SQLITE_OK) {
connection = nullptr;
return false;
}
int keyLen = static_cast<int>(strlen(key));
rc = sqlite3_key(connection, key, keyLen);
active = (rc == SQLITE_OK);
return active;
}
void SecureDB::Terminate()
{
if (resultGrid) {
sqlite3_free_table(resultGrid);
resultGrid = nullptr;
}
if (connection) {
sqlite3_close(connection);
connection = nullptr;
}
active = false;
}
bool SecureDB::Execute(const char* statement)
{
char* errMsg = nullptr;
int rc = sqlite3_exec(connection, statement,
Callback, nullptr, &errMsg);
if (rc != SQLITE_OK) {
if (errMsg) sqlite3_free(errMsg);
return false;
}
return true;
}
bool SecureDB::Fetch(const char* query, int& rows, int& cols)
{
if (resultGrid) {
sqlite3_free_table(resultGrid);
resultGrid = nullptr;
}
char* errMsg = nullptr;
int rc = sqlite3_get_table(connection, query,
&resultGrid, &rows, &cols, &errMsg);
if (rc != SQLITE_OK) {
if (errMsg) sqlite3_free(errMsg);
return false;
}
return true;
}
int SecureDB::Callback(void* context, int count,
char** values, char** names)
{
for (int i = 0; i < count; i++) {
// 필드 처리: names[i], values[i]
}
return 0;
}
빌드 및 배포
리소스 편집기나 마법사 기반 개발을 최소화하고 순수 코드로 UI를 구성하면 런타임 의존성이 추가로 줄어듭니다. 정적 링크 설정 후 Release 모드로 컴파일하면 CRT 런타임을 포함한 단일 실행파일이 생성됩니다. 이 바이너리는 Windows XP SP3부터 Windows 11까지 추가 DLL 없이 실행됩니다.