2010. 12. 26. 03:18

ini 파일을 암호화하여 읽고 쓰기


ini 파일을 이용하면 제품의 설정을 쉽게 읽고 쓸 수 있습니다. 물론, 레지스트리가 사용되기도 하지만, 레지스트리 보다는 WritePrivateProfileString(...) / GetPrivateProfileString(...)만 이용하므로 사용하기도 쉽고, 제품의 삭제시에도 ::DeleteFile(...) 해버리면 시스템에서 삭제되는등... 여러가지 장점이 있습니다.

이렇게 편리한 ini를 사용하면서, 아래와 같은 고민을 한 경우가 있을 것입니다.
내가 저장한 ini 파일을 남들에게 읽히고 싶지 않다.

왜냐하면, ini 파일은 plain-text 기반이라 다른이에 의해 쉽게 읽을 수 있기 때문입니다.
해당 .ini 파일의 ACL등을 조절하여, 파일의 접근을 막는 방법도 있겠지만, 좋은 방법은 아닙니다.
아마도 가장 좋은 방법은, ini 파일을 암호화 하는 것이지 않을까요.

이러한 요구조건에 의해, CSecureIni class를 만들었으며, 해당 class를 공유합니다.
암/복호화 알고리즘은 대략 아래와 같은 blowfish를 사용하였습니다.
http://www.schneier.com/blowfish.html
* Block cipher: 64-bit block 
* Variable key length: 32 bits to 448 bits 
* Designed by Bruce Schneier 
* Much faster than DES and IDEA 
* Unpatented and royalty-free 
* No license required 
* Free source code available
와 같습니다.

1>...\secureini.cpp(153) : error C2065: '보안을' : undeclared identifier
1>...\secureini.cpp(153) : error C2146: syntax error : missing '}' before identifier '위해'
1>...\secureini.cpp(153) : error C2146: syntax error : missing ';' before identifier '위해'
...
와 같은 오류가 발생합니다.
첨부된 SecureIni.cpp 의
void CSecureIni::CBlowfish::Reset()
{
 unsigned int SB_Init[NUM_S_BOXES][NUM_ENTRIES] =
 {
  보안을 위해 아래 값들을 되도록 섞으세요.
  shuffle below value for your security
  
  0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
...
의 위 "보안을~"과 "shuffle~" 줄을 삭제해야 컴파일이 됩니다.
단, 아래의 blowfish 인코딩 테이블이 있는데, 해당 값을 좀 섞어서 사용하시는것이 보안에 좋을 듯 합니다.
즉,
  0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
  0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
  ..
을,
  0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
  0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
  ..
와 같이 하시면 됩니다. 물론, 값을 변경해도 무방합니다.

CSecureIni는 다음과 같은 함수가 Export되어 있습니다. (static 함수이니, 멤버 변수로 만들 필요 없습니다.)

DWORD
GetPrivateProfileStringSec
IN LPCTSTR lpszAppName
IN LPCTSTR lpszKeyName
OPTIONAL IN LPCTSTR lpszDefault
OUT LPTSTR lpszReturnedString
IN DWORD dwCchSize
IN LPCTSTR lpszFileName
IN OPTIONAL LPCSTR lpszEncryptKey
IN OPTIONAL LPCSTR lpszIniHeader
리턴값과 lpszAppName ~ lpszFileName까지는 ::GetPrivateProfileString(...)과 같으니, MSDN을 참고하시기 바랍니다.

BOOL
WritePrivateProfileStringSec
IN LPCTSTR lpszAppName
IN LPCTSTR lpszKeyName
IN LPCTSTR lpszString
IN LPCTSTR lpszFileName
IN OPTIONAL LPCSTR lpszEncryptKey
IN OPTIONAL LPCSTR lpszIniHeader
리턴값과 lpszAppName ~ lpszFileName까지는 ::WritePrivateProfileString(...)과 같으니, MSDN을 참고하시기 바랍니다.

위 두개에서 Win32 API와 비교하여 추가된 옵션이 Key와 Header 입니다.
  • lpszEncryptKey
    ; 암호키를 전달합니다. CHAR로 정의된 ANSI 문자열입니다. 물론, BYTE 형태로 전달 가능합니다.
    ; NULL도 가능한데, NULL인 경우에는 plain-text로 저장하게 됩니다.
    ; "mykey"로 hardcode하면, procexp등에서 String 보기등으로 노출될 수 있습니다. 되도록 동적으로 구하십시요.
    ; NULL인 경우 plain-text로 저장합니다. 물론, Unicode로 저장합니다. 해당 .ini 파일은 HEADER 때문에 bom이 지원되지 않기 때문에, 노트패드등으로 열면, 깨진 문자로 볼 수 있습니다.
    ; "XXX"로 저장된 ini를 "TTT"로 새로운 값을 추가하면 ini 파일 문서 자체가 깨질 수 있으니 주의하시기 바랍니다.
  • lpszIniHeader
    ; ini 파일의 선두 16 byte에 저장할 문자열(ANSI)을 지정합니다.
    ; 암호화되어 있어도, ini 파일을 노트패드로 열면 Header는 바로 확인할 수 있습니다. 즉, Header는 암호화되지 않습니다.
    ; "iniver1.0" 등으로 ini 파일의 버전등을 저장하는데 응용할 수 있습니다.
    ; 기존의 ini 파일이 있는 경우, Header 값이 다른 WritePrivateProfileStringSec(...) 요청은 실패하게 됩니다.
DWORD
GetHeader
IN LPCTSTR lpszFileName
OUT LPSTR lpszHeaderA
IN DWORD dwCchLength => CCH_HEADER_SPACE

리턴값 : Win32 API Return Code
  • 파일의 Header를 구합니다.
  • 리턴값 : Win32 API Return Code
  • lpszFileName : ini 파일 명
  • lpszHeaderA : 헤더를 구할 CHAR Ansi 버퍼
  • dwCchLength : lpszHeaderA의 Cch 값. CCH_HEADER_SPACE 이여야 한다.
    즉, lpszHeaderA는 CHAR szBuf[CCH_HEADER_SPACE] = {0,}; 와 같아야 한다.
사용예)
#include <secureini.h>
...
CHAR szKeyA[MAX_KEY] = {0,};
StringCchPrintfA(szKeyA, MAX_KEY, "%c%c%c%c%c", 'A', 'B', 'C', 'D');
CSecureIni::WritePrivateProfileString(TEXT("app"), TEXT("key"), TEXT("val"), TEXT("C:\\a.ini"), szKeyA, "APPVER1.0");
CSecureIni::WritePrivateProfileString(TEXT("app"), TEXT("k), TEXT("v"), TEXT("C:\\a.ini"), szKeyA, "APPVER1.0");
...
CHAR szHeaderA[CCH_HEADER_SPACE] = {0,};
CSecureIni::GetHeader(TEXT("C:\\a.ini"), szHeaderA, CCH_HEADER_SPACE);
if (0 != _stricmp("APPVER1.0", szHeaderA))
{
// 에러 처리
goto FINAL;
}
...
TCHAR szVal[MAX_PATH] = {0,};
CSecureIni::GetPrivateProfileString(TEXT("app"), TEXT("key"), NULL, szVal, MAX_PATH, TEXT("C:\\a.ini"), szKeyA, "APPVER1.0");

SecureIni.h에 몇몇 define이 있는데, 제약 사항등을 조절할 수 있습니다.
(현재 ini 파일의 max 크기를 4MByte로 제한하였습니다.)

그리고, 각 파일명에 Hash 값으로 결정된 이름의 Mutex를 사용합니다. 즉, Thread/Process Safe 하도록 설계되었습니다.

속도 이슈가 발생할 수 있으므로, 되도록 적은양의 데이터가 적합하며, read/write가 빈번하지 않도록 모델링하시기 바랍니다.

참고로,
CSecureIni::CBlowfish cBlowfish;
cBlowfish.Set_Passwd("...");
cBlowfish.Encrypt(pbBuf, 8byte 단위의 cb 크기);
cBlowfish.Decrypt(pbBuf, 8byte 단위의 cb 크기);
와 같이 blowfish 알고리즘을 직접 접근하여 사용할 수도 있습니다.

keyword : Secure Encoding Ini WritePrivateProfileString GetPrivateProfileString