This commit is contained in:
암냥 2026-06-25 13:48:22 +09:00
commit 303192b198
9 changed files with 1154 additions and 34 deletions

113
.gitignore vendored
View file

@ -2,4 +2,117 @@ tests/*
!test/test.sh !test/test.sh
build/ build/
.vscode .vscode
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# debug information files
*.dwo
# General
.DS_Store .DS_Store
.localized
__MACOSX/
.AppleDouble
.LSOverride
Icon[]
# Resource forks
._*
# Files and directories that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
.com.apple.timemachine.supported
.PKInstallSandboxManager
.PKInstallSandboxManager-SystemSoftware
.hotfiles.btree
.vol
.file
.disk_label*
lost+found
.HFS+ Private Directory Data[]
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Mac OS 6 to 9
Desktop DB
Desktop DF
TheFindByContentFolder
TheVolumeSettingsFolder
.FBCIndex
.FBCSemaphoreFile
.FBCLockFolder
# Quota system
.quota.group
.quota.user
.quota.ops.group
.quota.ops.user
# TimeMachine
Backups.backupdb
.MobileBackups
.MobileBackups.trash
MobileBackups.trash
tmbootpicker.efi

View file

@ -1,29 +1,37 @@
# 컴파일러와 플래그 기본값은 환경 변수나 make 인자로 덮어쓸 수 있습니다.
CC ?= cc CC ?= cc
CPPFLAGS ?= -Iinclude CPPFLAGS ?= -Iinclude
CFLAGS ?= -std=c11 -O2 -Wall -Wextra -Wpedantic CFLAGS ?= -std=c11 -O2 -Wall -Wextra -Wpedantic
LDLIBS ?= LDLIBS ?=
# Windows 빌드는 호환 컴파일러/링커를 사용할 때 bcrypt가 필요합니다.
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
LDLIBS += -lbcrypt LDLIBS += -lbcrypt
endif endif
# 최종 실행 파일 경로, 소스 목록, 여기서 파생되는 오브젝트 파일 경로입니다.
TARGET := build/gerbera TARGET := build/gerbera
SOURCES := src/main.c src/encrypt.c src/decrypt.c src/cipher.c SOURCES := src/main.c src/encrypt.c src/decrypt.c src/cipher.c
OBJECTS := $(SOURCES:src/%.c=build/%.o) OBJECTS := $(SOURCES:src/%.c=build/%.o)
# 모든 오브젝트 파일을 컴파일한 뒤 실행 파일을 링크합니다.
$(TARGET): $(OBJECTS) $(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)
# 각 C 파일을 build/<name>.o로 컴파일하며, 필요하면 build/를 먼저 만듭니다.
build/%.o: src/%.c include/gerbera.h | build build/%.o: src/%.c include/gerbera.h | build
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
# 컴파일이나 링크 전에 출력 디렉터리가 존재하도록 보장합니다.
build: build:
mkdir -p build mkdir -p build
.PHONY: clean test .PHONY: clean test
# 방금 빌드한 실행 파일로 셸 테스트 모음을 실행합니다.
test: $(TARGET) test: $(TARGET)
GERBERA_BIN=./$(TARGET) sh tests/test.sh GERBERA_BIN=./$(TARGET) sh tests/test.sh
# 생성된 모든 빌드 산출물을 삭제합니다.
clean: clean:
rm -rf build rm -rf build

View file

@ -1,16 +1,23 @@
#ifndef GERBERA_H #ifndef GERBERA_H
#define GERBERA_H #define GERBERA_H
/* 명령줄 진입점과 각 모듈이 함께 쓰는 공개 선언입니다. */
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
/* Gerbera 파일 형식과 메모리 작업 버퍼에서 사용하는 고정 크기입니다. */
#define GERBERA_MAGIC_SIZE 10 #define GERBERA_MAGIC_SIZE 10
#define GERBERA_NONCE_SIZE 16 #define GERBERA_NONCE_SIZE 16
#define GERBERA_TAG_SIZE 16 #define GERBERA_TAG_SIZE 16
#define GERBERA_BUFFER_SIZE 4096 #define GERBERA_BUFFER_SIZE 4096
/* 헤더에는 매직 바이트, nonce, 원문 길이가 저장됩니다. */
#define GERBERA_HEADER_SIZE (GERBERA_MAGIC_SIZE + GERBERA_NONCE_SIZE + 8) #define GERBERA_HEADER_SIZE (GERBERA_MAGIC_SIZE + GERBERA_NONCE_SIZE + 8)
/* 암호화 파일은 데이터 앞에 헤더를, 뒤에 인증 태그를 붙입니다. */
#define GERBERA_OVERHEAD_SIZE (GERBERA_HEADER_SIZE + GERBERA_TAG_SIZE) #define GERBERA_OVERHEAD_SIZE (GERBERA_HEADER_SIZE + GERBERA_TAG_SIZE)
/* 스트림 암호 상태: 순열 테이블, 파생 키, 인덱스, 바이트 위치입니다. */
typedef struct { typedef struct {
uint8_t state[256]; uint8_t state[256];
uint8_t key[32]; uint8_t key[32];
@ -19,22 +26,34 @@ typedef struct {
uint64_t position; uint64_t position;
} GerberaCipher; } GerberaCipher;
/* 고정 크기 인증 태그를 계산하고 검증하는 상태입니다. */
typedef struct { typedef struct {
uint8_t state[32]; uint8_t state[32];
uint64_t length; uint64_t length;
} GerberaAuth; } GerberaAuth;
/* 비밀번호와 파일별 nonce로 암호 스트림을 초기화합니다. */
void gerbera_cipher_init(GerberaCipher *ctx, const char *password, void gerbera_cipher_init(GerberaCipher *ctx, const char *password,
const uint8_t nonce[GERBERA_NONCE_SIZE]); const uint8_t nonce[GERBERA_NONCE_SIZE]);
/* 생성된 키스트림을 data에 XOR합니다. 다시 호출하면 스트림이 이어집니다. */
void gerbera_cipher_apply(GerberaCipher *ctx, uint8_t data[], size_t length); void gerbera_cipher_apply(GerberaCipher *ctx, uint8_t data[], size_t length);
/* 같은 비밀번호와 nonce로 인증 누산기를 초기화합니다. */
void gerbera_auth_init(GerberaAuth *ctx, const char *password, void gerbera_auth_init(GerberaAuth *ctx, const char *password,
const uint8_t nonce[GERBERA_NONCE_SIZE]); const uint8_t nonce[GERBERA_NONCE_SIZE]);
/* 파일에 기록되는 순서대로 바이트를 인증 누산기에 넣습니다. */
void gerbera_auth_update(GerberaAuth *ctx, const uint8_t data[], size_t length); void gerbera_auth_update(GerberaAuth *ctx, const uint8_t data[], size_t length);
/* 누산을 마무리하고 인증 태그를 씁니다. */
void gerbera_auth_finish(GerberaAuth *ctx, uint8_t tag[GERBERA_TAG_SIZE]); void gerbera_auth_finish(GerberaAuth *ctx, uint8_t tag[GERBERA_TAG_SIZE]);
/* 첫 차이에서 바로 끝내지 않고 태그 전체를 비교합니다. */
int gerbera_constant_time_equal(const uint8_t a[], const uint8_t b[], int gerbera_constant_time_equal(const uint8_t a[], const uint8_t b[],
size_t length); size_t length);
/* 파일 전체를 암호화하거나 복호화하며, 성공 시 0, 오류 시 0이 아닌 값을 반환합니다. */
int encrypt_file(const char *input_path, const char *output_path, int encrypt_file(const char *input_path, const char *output_path,
const char *password); const char *password);
int decrypt_file(const char *input_path, const char *output_path, int decrypt_file(const char *input_path, const char *output_path,

View file

@ -1,21 +1,28 @@
@echo off @echo off
rem 환경 변경이 이 스크립트 실행 안에만 머물도록 합니다.
setlocal setlocal
rem Makefile과 POSIX 스크립트처럼 실행 파일을 build\에 둡니다.
if not exist build mkdir build if not exist build mkdir build
rem Visual Studio 컴파일러가 있으면 우선 사용합니다.
where cl >nul 2>nul where cl >nul 2>nul
if %errorlevel% equ 0 goto build_msvc if %errorlevel% equ 0 goto build_msvc
rem GCC가 설치되어 있고 PATH에서 보이면 다음 후보로 사용합니다.
where gcc >nul 2>nul where gcc >nul 2>nul
if %errorlevel% equ 0 goto build_gcc if %errorlevel% equ 0 goto build_gcc
rem 세 번째 지원 Windows 컴파일러로 Clang을 사용합니다.
where clang >nul 2>nul where clang >nul 2>nul
if %errorlevel% equ 0 goto build_clang if %errorlevel% equ 0 goto build_clang
rem 지원되는 컴파일러를 찾지 못했으므로 명확한 메시지를 출력하고 중단합니다.
echo C compiler not found. Install Visual Studio Build Tools, GCC, or Clang. echo C compiler not found. Install Visual Studio Build Tools, GCC, or Clang.
exit /b 1 exit /b 1
:build_msvc :build_msvc
rem MSVC는 build\ 안에 gerbera.exe를 쓰고 bcrypt.lib를 명시적으로 링크합니다.
pushd build pushd build
cl /nologo /std:c11 /O2 /W4 /I..\include ^ cl /nologo /std:c11 /O2 /W4 /I..\include ^
..\src\main.c ..\src\encrypt.c ..\src\decrypt.c ..\src\cipher.c ^ ..\src\main.c ..\src\encrypt.c ..\src\decrypt.c ..\src\cipher.c ^
@ -26,6 +33,7 @@ if not %result% equ 0 exit /b %result%
goto success goto success
:build_gcc :build_gcc
rem GCC는 POSIX 빌드와 같은 경고 옵션을 쓰고 bcrypt를 링크합니다.
gcc -Iinclude -std=c11 -O2 -Wall -Wextra -Wpedantic ^ gcc -Iinclude -std=c11 -O2 -Wall -Wextra -Wpedantic ^
src\main.c src\encrypt.c src\decrypt.c src\cipher.c ^ src\main.c src\encrypt.c src\decrypt.c src\cipher.c ^
-o build\gerbera.exe -lbcrypt -o build\gerbera.exe -lbcrypt
@ -33,11 +41,13 @@ if not %errorlevel% equ 0 exit /b %errorlevel%
goto success goto success
:build_clang :build_clang
rem Clang은 이 작은 프로젝트에서 GCC 방식 명령줄을 그대로 따릅니다.
clang -Iinclude -std=c11 -O2 -Wall -Wextra -Wpedantic ^ clang -Iinclude -std=c11 -O2 -Wall -Wextra -Wpedantic ^
src\main.c src\encrypt.c src\decrypt.c src\cipher.c ^ src\main.c src\encrypt.c src\decrypt.c src\cipher.c ^
-o build\gerbera.exe -lbcrypt -o build\gerbera.exe -lbcrypt
if not %errorlevel% equ 0 exit /b %errorlevel% if not %errorlevel% equ 0 exit /b %errorlevel%
:success :success
rem 모든 컴파일러 선택지가 공유하는 성공 경로입니다.
echo Build complete: build\gerbera.exe echo Build complete: build\gerbera.exe
exit /b 0 exit /b 0

View file

@ -1,9 +1,15 @@
#!/bin/sh #!/bin/sh
# 실패한 명령이나 정의되지 않은 변수에서 즉시 종료합니다.
set -eu set -eu
# 호출자가 CC=...로 컴파일러를 고를 수 있고, 기본값은 cc입니다.
compiler="${CC:-cc}" compiler="${CC:-cc}"
# 컴파일 산출물은 소스 트리 밖의 build/에 둡니다.
mkdir -p build mkdir -p build
# 모든 C 번역 단위를 묶어 Gerbera 명령 하나를 빌드합니다.
"$compiler" \ "$compiler" \
-Iinclude \ -Iinclude \
-std=c11 \ -std=c11 \
@ -17,4 +23,5 @@ mkdir -p build
src/cipher.c \ src/cipher.c \
-o build/gerbera -o build/gerbera
# 실행 파일이 어디에 만들어졌는지 알려 줍니다.
echo "Build complete: build/gerbera" echo "Build complete: build/gerbera"

View file

@ -1,145 +1,446 @@
#include "gerbera.h" #include "gerbera.h" /* GerberaCipher, GerberaAuth, 상수 크기 등을 정의한 헤더 */
#include <string.h> #include <string.h> /* strlen(), memcpy() 사용 */
/* uint8_t는 8비트 정수이므로, 이 함수는 1바이트 값을 왼쪽으로 회전합니다. */
static uint8_t rotate_left8(uint8_t value, unsigned count) static uint8_t rotate_left8(uint8_t value, unsigned count)
{ {
/* count가 8 이상이면 8비트 회전에 맞게 0~7 범위로 줄입니다. */
count &= 7U; count &= 7U;
/*
* count만큼 ,
* 8-count만큼 .
*
* ((8U - count) & 7U) :
* count가 0 value >> 8 C에서 ,
* 8 0 .
*/
return (uint8_t)((value << count) | (value >> ((8U - count) & 7U))); return (uint8_t)((value << count) | (value >> ((8U - count) & 7U)));
} }
/* /*
* Password bytes are folded into a 32-byte array many times. This is an * nonce를 32 .
* educational, original construction; it is not a replacement for a *
* reviewed password KDF such as Argon2. * key : 32
* password :
* nonce :
* domain :
*/ */
static void derive_key(uint8_t key[32], const char *password, static void derive_key(uint8_t key[32], const char *password,
const uint8_t nonce[GERBERA_NONCE_SIZE], uint8_t domain) const uint8_t nonce[GERBERA_NONCE_SIZE], uint8_t domain)
{ {
/*
* 32 .
* 0 ,
* 0 .
*/
static const uint8_t initial[32] = { static const uint8_t initial[32] = {
0x41, 0x6b, 0x79, 0x61, 0x6d, 0x61, 0x4d, 0x69, 0x41, 0x6b, 0x79, 0x61, 0x6d, 0x61, 0x4d, 0x69,
0x7a, 0x75, 0x6b, 0x69, 0x4e, 0x41, 0x45, 0x31, 0x7a, 0x75, 0x6b, 0x69, 0x4e, 0x41, 0x45, 0x31,
0x9d, 0x37, 0x6b, 0xa1, 0xf4, 0x52, 0xc8, 0x0e, 0x9d, 0x37, 0x6b, 0xa1, 0xf4, 0x52, 0xc8, 0x0e,
0x73, 0xbf, 0x25, 0xda, 0x86, 0x19, 0xe0, 0x4c 0x73, 0xbf, 0x25, 0xda, 0x86, 0x19, 0xe0, 0x4c
}; };
/* 비밀번호 길이를 미리 구해 반복문에서 사용합니다. */
size_t password_length = strlen(password); size_t password_length = strlen(password);
/* 키 파생을 여러 번 반복하기 위한 라운드 변수입니다. */
unsigned round; unsigned round;
/* 비밀번호의 각 바이트를 순회하기 위한 인덱스입니다. */
size_t p; size_t p;
/* key 배열을 initial 값으로 초기화합니다. */
memcpy(key, initial, sizeof(initial)); memcpy(key, initial, sizeof(initial));
/*
* domain key[0] .
* password와 nonce를 ,
* .
*/
key[0] ^= domain; key[0] ^= domain;
/*
* nonce를 8192 .
* ,
* .
*/
for (round = 0; round < 8192U; ++round) { for (round = 0; round < 8192U; ++round) {
/* 비밀번호의 모든 바이트를 하나씩 키 상태에 섞습니다. */
for (p = 0; p < password_length; ++p) { for (p = 0; p < password_length; ++p) {
/*
* a는 key .
* & 31U 32 0~31 .
*/
size_t a = (p + round) & 31U; size_t a = (p + round) & 31U;
/* b는 a에서 11칸 떨어진 위치입니다. */
size_t b = (a + 11U) & 31U; size_t b = (a + 11U) & 31U;
/* c는 a에서 23칸 떨어진 위치입니다. */
size_t c = (a + 23U) & 31U; size_t c = (a + 23U) & 31U;
/*
* mixed는 , , nonce ,
* .
*
* uint8_t로 0~255 .
*/
uint8_t mixed = (uint8_t)(key[b] + (uint8_t)password[p] + uint8_t mixed = (uint8_t)(key[b] + (uint8_t)password[p] +
nonce[(p + round) & 15U] + nonce[(p + round) & 15U] +
(uint8_t)round); (uint8_t)round);
/*
* key[a] .
*
* 1. key[a] mixed를 XOR합니다.
* 2. key[c] 3 .
* 3. key[c] .
*
* key[a] password, nonce, round, key .
*/
key[a] = (uint8_t)(rotate_left8((uint8_t)(key[a] ^ mixed), key[a] = (uint8_t)(rotate_left8((uint8_t)(key[a] ^ mixed),
(unsigned)(key[c] & 7U)) + key[c]); (unsigned)(key[c] & 7U)) + key[c]);
} }
/*
* ,
* key .
*/
key[round & 31U] ^= rotate_left8(key[(round + 17U) & 31U], round & 7U); key[round & 31U] ^= rotate_left8(key[(round + 17U) & 31U], round & 7U);
} }
} }
/*
* .
*
* ctx :
* password :
* nonce : nonce
*/
void gerbera_cipher_init(GerberaCipher *ctx, const char *password, void gerbera_cipher_init(GerberaCipher *ctx, const char *password,
const uint8_t nonce[GERBERA_NONCE_SIZE]) const uint8_t nonce[GERBERA_NONCE_SIZE])
{ {
/* 0~255 순열 테이블을 초기화할 때 쓰는 인덱스입니다. */
unsigned n; unsigned n;
/* RC4 계열 알고리즘에서 사용하는 두 번째 인덱스 역할입니다. */
uint8_t j = 0; uint8_t j = 0;
/*
* .
* domain 0x43 cipher의 C라고 .
*/
derive_key(ctx->key, password, nonce, 0x43U); derive_key(ctx->key, password, nonce, 0x43U);
/*
* state 0, 1, 2, ..., 255 .
* .
*/
for (n = 0; n < 256U; ++n) { for (n = 0; n < 256U; ++n) {
ctx->state[n] = (uint8_t)n; ctx->state[n] = (uint8_t)n;
} }
/*
* key와 nonce를 state .
* 256 1024 .
*/
for (n = 0; n < 1024U; ++n) { for (n = 0; n < 1024U; ++n) {
/*
* i는 state .
* n이 256 uint8_t로 0~255 .
*/
uint8_t i = (uint8_t)n; uint8_t i = (uint8_t)n;
/* state[i]와 state[j]를 교환할 때 임시로 저장하는 변수입니다. */
uint8_t temporary; uint8_t temporary;
/*
* j를 .
*
* j, state[i], key , nonce j .
* uint8_t라서 255 0~255 wrap .
*/
j = (uint8_t)(j + ctx->state[i] + ctx->key[n & 31U] + j = (uint8_t)(j + ctx->state[i] + ctx->key[n & 31U] +
nonce[n & 15U]); nonce[n & 15U]);
/* state[i] 값을 임시 변수에 저장합니다. */
temporary = ctx->state[i]; temporary = ctx->state[i];
/* state[j] 값을 state[i] 위치로 옮깁니다. */
ctx->state[i] = ctx->state[j]; ctx->state[i] = ctx->state[j];
/* 임시로 저장했던 원래 state[i] 값을 state[j]에 넣습니다. */
ctx->state[j] = temporary; ctx->state[j] = temporary;
} }
/* 키스트림 생성에 사용할 i 인덱스를 0으로 초기화합니다. */
ctx->i = 0; ctx->i = 0;
/*
* j는 .
* j .
*/
ctx->j = j; ctx->j = j;
/* 지금까지 처리한 바이트 위치를 0으로 초기화합니다. */
ctx->position = 0; ctx->position = 0;
} }
/*
* data .
*
* XOR .
*/
void gerbera_cipher_apply(GerberaCipher *ctx, uint8_t data[], size_t length) void gerbera_cipher_apply(GerberaCipher *ctx, uint8_t data[], size_t length)
{ {
/* data의 각 바이트를 순회하기 위한 인덱스입니다. */
size_t n; size_t n;
/* 입력 데이터 길이만큼 한 바이트씩 처리합니다. */
for (n = 0; n < length; ++n) { for (n = 0; n < length; ++n) {
/* state 값을 교환할 때 사용할 임시 변수입니다. */
uint8_t temporary; uint8_t temporary;
/* 이번 위치에서 생성된 키스트림 바이트입니다. */
uint8_t stream; uint8_t stream;
/* i를 한 칸 전진시킵니다. */
ctx->i = (uint8_t)(ctx->i + 1U); ctx->i = (uint8_t)(ctx->i + 1U);
/*
* j도 .
*
* state[i] key의
* .
*/
ctx->j = (uint8_t)(ctx->j + ctx->state[ctx->i] + ctx->j = (uint8_t)(ctx->j + ctx->state[ctx->i] +
ctx->key[ctx->position & 31U]); ctx->key[ctx->position & 31U]);
/* state[i]를 임시 변수에 저장합니다. */
temporary = ctx->state[ctx->i]; temporary = ctx->state[ctx->i];
/* state[j] 값을 state[i] 위치로 옮깁니다. */
ctx->state[ctx->i] = ctx->state[ctx->j]; ctx->state[ctx->i] = ctx->state[ctx->j];
/* 원래 state[i] 값을 state[j]에 넣어 두 값을 교환합니다. */
ctx->state[ctx->j] = temporary; ctx->state[ctx->j] = temporary;
/*
* state[i] state[j]
* state .
*/
stream = ctx->state[(uint8_t)(ctx->state[ctx->i] + stream = ctx->state[(uint8_t)(ctx->state[ctx->i] +
ctx->state[ctx->j])]; ctx->state[ctx->j])];
/*
* key XOR합니다.
*
* (ctx->position + stream) & 31U:
* stream key의 0~31 .
*
* ctx->position & 7U:
* 0~7 .
*/
stream ^= rotate_left8(ctx->key[(ctx->position + stream) & 31U], stream ^= rotate_left8(ctx->key[(ctx->position + stream) & 31U],
(unsigned)(ctx->position & 7U)); (unsigned)(ctx->position & 7U));
/*
* XOR합니다.
*
* :
* ^ =
*
* :
* ^ =
*/
data[n] ^= stream; data[n] ^= stream;
/* 처리한 바이트 수를 1 증가시킵니다. */
++ctx->position; ++ctx->position;
} }
} }
/*
* .
*
* .
*/
void gerbera_auth_init(GerberaAuth *ctx, const char *password, void gerbera_auth_init(GerberaAuth *ctx, const char *password,
const uint8_t nonce[GERBERA_NONCE_SIZE]) const uint8_t nonce[GERBERA_NONCE_SIZE])
{ {
/*
* state를 .
* domain 0x41 auth의 A라고 .
*
* domain과 ,
* password와 nonce를 .
*/
derive_key(ctx->state, password, nonce, 0x41U); derive_key(ctx->state, password, nonce, 0x41U);
/* 아직 처리한 데이터가 없으므로 길이는 0입니다. */
ctx->length = 0; ctx->length = 0;
} }
/*
* .
*
* data의 ctx->state에 ,
* tag .
*/
void gerbera_auth_update(GerberaAuth *ctx, const uint8_t data[], size_t length) void gerbera_auth_update(GerberaAuth *ctx, const uint8_t data[], size_t length)
{ {
/* data 배열을 순회할 인덱스입니다. */
size_t n; size_t n;
/* 입력 데이터를 한 바이트씩 처리합니다. */
for (n = 0; n < length; ++n) { for (n = 0; n < length; ++n) {
/*
* a는 state .
* 0~31 .
*/
size_t a = (size_t)(ctx->length & 31U); size_t a = (size_t)(ctx->length & 31U);
/* b는 a에서 7칸 떨어진 위치입니다. */
size_t b = (a + 7U) & 31U; size_t b = (a + 7U) & 31U;
/* c는 a에서 19칸 떨어진 위치입니다. */
size_t c = (a + 19U) & 31U; size_t c = (a + 19U) & 31U;
/*
* state[a] .
*
* 1. state[a]
* 2. data[n]
* 3. state[c]
*
* XOR한 ,
* state[b] 3 .
* state[b] a state[a] .
*/
ctx->state[a] = (uint8_t)(rotate_left8( ctx->state[a] = (uint8_t)(rotate_left8(
(uint8_t)(ctx->state[a] ^ data[n] ^ ctx->state[c]), (uint8_t)(ctx->state[a] ^ data[n] ^ ctx->state[c]),
(unsigned)(ctx->state[b] & 7U)) + ctx->state[b] + (uint8_t)a); (unsigned)(ctx->state[b] & 7U)) + ctx->state[b] + (uint8_t)a);
/*
* state[a]
* state[c] .
*
* state ,
* .
*/
ctx->state[c] ^= rotate_left8((uint8_t)(data[n] + ctx->state[a]), ctx->state[c] ^= rotate_left8((uint8_t)(data[n] + ctx->state[a]),
(unsigned)(a & 7U)); (unsigned)(a & 7U));
/* 전체 처리 길이를 1 증가시킵니다. */
++ctx->length; ++ctx->length;
} }
} }
/*
* auth_update로 .
*/
void gerbera_auth_finish(GerberaAuth *ctx, uint8_t tag[GERBERA_TAG_SIZE]) void gerbera_auth_finish(GerberaAuth *ctx, uint8_t tag[GERBERA_TAG_SIZE])
{ {
/*
* 8 little-endian .
* , 0 .
*/
uint8_t trailer[8]; uint8_t trailer[8];
/* 반복문에서 사용할 인덱스입니다. */
unsigned n; unsigned n;
/*
* ctx->length를 8 trailer에 .
*
* n = 0 8,
* n = 1 8,
* little-endian .
*/
for (n = 0; n < 8U; ++n) { for (n = 0; n < 8U; ++n) {
trailer[n] = (uint8_t)(ctx->length >> (n * 8U)); trailer[n] = (uint8_t)(ctx->length >> (n * 8U));
} }
/*
* .
* .
*/
gerbera_auth_update(ctx, trailer, sizeof(trailer)); gerbera_auth_update(ctx, trailer, sizeof(trailer));
/*
* .
*
* 256 state .
* 32 8 .
*/
for (n = 0; n < 256U; ++n) { for (n = 0; n < 256U; ++n) {
/*
* n state ,
* n .
*/
uint8_t value = (uint8_t)(ctx->state[(n + 1U) & 31U] + uint8_t value = (uint8_t)(ctx->state[(n + 1U) & 31U] +
ctx->state[(n + 13U) & 31U] + n); ctx->state[(n + 13U) & 31U] + n);
/*
* value를 n에 0~7 ,
* state XOR하여 .
*/
ctx->state[n & 31U] ^= rotate_left8(value, n & 7U); ctx->state[n & 31U] ^= rotate_left8(value, n & 7U);
} }
/*
* state는 32,
* GERBERA_TAG_SIZE .
*
* 16 .
*/
for (n = 0; n < GERBERA_TAG_SIZE; ++n) { for (n = 0; n < GERBERA_TAG_SIZE; ++n) {
/*
* state 16 16 XOR해서
* tag .
*/
tag[n] = (uint8_t)(ctx->state[n] ^ ctx->state[n + 16U]); tag[n] = (uint8_t)(ctx->state[n] ^ ctx->state[n + 16U]);
} }
} }
/*
* .
*
* memcmp() ,
* .
*
* length .
*/
int gerbera_constant_time_equal(const uint8_t a[], const uint8_t b[], size_t length) int gerbera_constant_time_equal(const uint8_t a[], const uint8_t b[], size_t length)
{ {
/* 배열을 순회할 인덱스입니다. */
size_t n; size_t n;
/*
* .
* 0 .
*/
uint8_t difference = 0; uint8_t difference = 0;
/*
* .
* return하지 .
*/
for (n = 0; n < length; ++n) { for (n = 0; n < length; ++n) {
/*
* a[n] b[n] XOR 0.
* 0 .
*
* OR로 , difference는 0 .
*/
difference |= (uint8_t)(a[n] ^ b[n]); difference |= (uint8_t)(a[n] ^ b[n]);
} }
/*
* difference가 0 .
* 1, 0 .
*/
return difference == 0; return difference == 0;
} }

View file

@ -1,111 +1,376 @@
#include "gerbera.h" #include "gerbera.h" /* Gerbera 관련 타입, 상수, 함수 선언을 가져옵니다. */
#include <stdio.h> #include <stdio.h> /* FILE, fopen(), fread(), fwrite(), fprintf() 사용 */
#include <string.h> #include <string.h> /* memcmp() 사용 */
/*
* Gerbera .
* encrypt_file에서 magic .
*/
static const uint8_t expected_magic[GERBERA_MAGIC_SIZE] = static const uint8_t expected_magic[GERBERA_MAGIC_SIZE] =
{'M', 'I', 'Z', 'U', 'K', 'I', 0xF0, 0x9F, 0x8E, 0x80}; {'M', 'I', 'Z', 'U', 'K', 'I', 0xF0, 0x9F, 0x8E, 0x80};
/*
* little-endian 8 uint64_t .
*
* bytes:
* 8
*/
static uint64_t decode_u64_le(const uint8_t bytes[8]) static uint64_t decode_u64_le(const uint8_t bytes[8])
{ {
/* 최종적으로 조립될 64비트 정수입니다. */
uint64_t value = 0; uint64_t value = 0;
/* 0번 바이트부터 7번 바이트까지 순회하기 위한 변수입니다. */
unsigned n; unsigned n;
/* 낮은 바이트부터 높은 바이트까지 순서대로 조립합니다. */
for (n = 0; n < 8U; ++n) { for (n = 0; n < 8U; ++n) {
/*
* bytes[n] uint64_t로 ,
* n * 8 .
*
* OR value에 8 64 .
*/
value |= (uint64_t)bytes[n] << (n * 8U); value |= (uint64_t)bytes[n] << (n * 8U);
} }
/* 조립된 64비트 값을 반환합니다. */
return value; return value;
} }
/*
* input_path에 Gerbera output_path에 .
*
* :
* 0 =
* 1 =
*/
int decrypt_file(const char *input_path, const char *output_path, int decrypt_file(const char *input_path, const char *output_path,
const char *password) const char *password)
{ {
/* 암호문 입력 파일 포인터입니다. 아직 열지 않았으므로 NULL로 초기화합니다. */
FILE *input = NULL; FILE *input = NULL;
/* 복호화 결과를 쓸 출력 파일 포인터입니다. */
FILE *output = NULL; FILE *output = NULL;
/* 파일에서 읽은 magic 값을 저장합니다. */
uint8_t magic[GERBERA_MAGIC_SIZE]; uint8_t magic[GERBERA_MAGIC_SIZE];
/* 파일에서 읽은 nonce를 저장합니다. */
uint8_t nonce[GERBERA_NONCE_SIZE]; uint8_t nonce[GERBERA_NONCE_SIZE];
/* 파일에서 읽은 원문 크기 필드 8바이트를 저장합니다. */
uint8_t size_bytes[8]; uint8_t size_bytes[8];
/* 암호문을 청크 단위로 읽고 복호화할 임시 버퍼입니다. */
uint8_t buffer[GERBERA_BUFFER_SIZE]; uint8_t buffer[GERBERA_BUFFER_SIZE];
/* 파일 끝에 저장되어 있던 인증 태그입니다. */
uint8_t stored_tag[GERBERA_TAG_SIZE]; uint8_t stored_tag[GERBERA_TAG_SIZE];
/* 현재 입력 파일 내용으로 다시 계산한 인증 태그입니다. */
uint8_t calculated_tag[GERBERA_TAG_SIZE]; uint8_t calculated_tag[GERBERA_TAG_SIZE];
/* 복호화에 사용할 스트림 암호 상태입니다. */
GerberaCipher cipher; GerberaCipher cipher;
/* 인증 태그 검증에 사용할 인증 상태입니다. */
GerberaAuth auth; GerberaAuth auth;
/* 헤더에 기록된 원래 평문 크기입니다. */
uint64_t plain_size; uint64_t plain_size;
/* 아직 처리해야 할 암호문 본문 바이트 수입니다. */
uint64_t remaining; uint64_t remaining;
/* 전체 암호문 파일 크기입니다. ftell()이 long을 반환하므로 long을 사용합니다. */
long file_size; long file_size;
/* fread()로 실제 읽은 바이트 수입니다. */
size_t count; size_t count;
/*
* .
* 1 , 0 .
*/
int result = 1; int result = 1;
/*
* .
* "rb" .
*/
input = fopen(input_path, "rb"); input = fopen(input_path, "rb");
/*
* ,
* ,
* .
*
* .
*/
if (input == NULL || fseek(input, 0, SEEK_END) != 0 || if (input == NULL || fseek(input, 0, SEEK_END) != 0 ||
(file_size = ftell(input)) < 0 || fseek(input, 0, SEEK_SET) != 0) { (file_size = ftell(input)) < 0 || fseek(input, 0, SEEK_SET) != 0) {
/* 입력 파일을 열 수 없거나 크기를 구할 수 없다는 메시지입니다. */
fprintf(stderr, "Could not open the encrypted file.\n"); fprintf(stderr, "Could not open the encrypted file.\n");
/* 열린 파일이 있으면 닫기 위해 cleanup으로 이동합니다. */
goto cleanup; goto cleanup;
} }
/*
* Gerbera ,
* magic, nonce, size_bytes를 .
*
* magic이 expected_magic과 .
*/
if (file_size < GERBERA_OVERHEAD_SIZE || if (file_size < GERBERA_OVERHEAD_SIZE ||
fread(magic, 1, sizeof(magic), input) != sizeof(magic) || fread(magic, 1, sizeof(magic), input) != sizeof(magic) ||
fread(nonce, 1, sizeof(nonce), input) != sizeof(nonce) || fread(nonce, 1, sizeof(nonce), input) != sizeof(nonce) ||
fread(size_bytes, 1, sizeof(size_bytes), input) != sizeof(size_bytes) || fread(size_bytes, 1, sizeof(size_bytes), input) != sizeof(size_bytes) ||
memcmp(magic, expected_magic, sizeof(magic)) != 0) { memcmp(magic, expected_magic, sizeof(magic)) != 0) {
/*
* ,
* ,
* magic Gerbera .
*/
fprintf(stderr, "The input is not a Gerbera file.\n"); fprintf(stderr, "The input is not a Gerbera file.\n");
/* cleanup으로 이동합니다. */
goto cleanup; goto cleanup;
} }
/*
* 8 uint64_t로 .
*/
plain_size = decode_u64_le(size_bytes); plain_size = decode_u64_le(size_bytes);
/*
* Gerbera .
*
* header + encrypted_body + tag
*
* encrypted_body의 .
* XOR .
*/
if (plain_size != (uint64_t)(file_size - GERBERA_OVERHEAD_SIZE)) { if (plain_size != (uint64_t)(file_size - GERBERA_OVERHEAD_SIZE)) {
/* 헤더에 기록된 크기와 실제 파일 크기가 맞지 않는 경우입니다. */
fprintf(stderr, "The file size metadata is invalid.\n"); fprintf(stderr, "The file size metadata is invalid.\n");
/* cleanup으로 이동합니다. */
goto cleanup; goto cleanup;
} }
/* First pass: authenticate before any plaintext is written. */ /*
* .
* .
*/
gerbera_auth_init(&auth, password, nonce); gerbera_auth_init(&auth, password, nonce);
/*
* magic .
* .
*/
gerbera_auth_update(&auth, magic, sizeof(magic)); gerbera_auth_update(&auth, magic, sizeof(magic));
/*
* nonce .
* nonce가 .
*/
gerbera_auth_update(&auth, nonce, sizeof(nonce)); gerbera_auth_update(&auth, nonce, sizeof(nonce));
/*
* .
* size_bytes가 .
*/
gerbera_auth_update(&auth, size_bytes, sizeof(size_bytes)); gerbera_auth_update(&auth, size_bytes, sizeof(size_bytes));
/*
* remaining은 .
* .
*/
remaining = plain_size; remaining = plain_size;
/*
* .
* .
*/
while (remaining > 0) { while (remaining > 0) {
/*
* .
*
* buffer보다 ,
* buffer .
*/
size_t wanted = remaining < sizeof(buffer) ? (size_t)remaining : sizeof(buffer); size_t wanted = remaining < sizeof(buffer) ? (size_t)remaining : sizeof(buffer);
/*
* wanted .
*/
count = fread(buffer, 1, wanted, input); count = fread(buffer, 1, wanted, input);
/*
* wanted만큼 .
*/
if (count != wanted) { if (count != wanted) {
/* 암호문 데이터를 읽지 못했다는 메시지입니다. */
fprintf(stderr, "Could not read the encrypted data.\n"); fprintf(stderr, "Could not read the encrypted data.\n");
goto cleanup;
} /* cleanup으로 이동합니다. */
gerbera_auth_update(&auth, buffer, count);
remaining -= count;
}
if (fread(stored_tag, 1, sizeof(stored_tag), input) != sizeof(stored_tag)) {
fprintf(stderr, "The authentication tag is missing.\n");
goto cleanup;
}
gerbera_auth_finish(&auth, calculated_tag);
if (!gerbera_constant_time_equal(stored_tag, calculated_tag, sizeof(stored_tag))) {
fprintf(stderr, "The password is incorrect or the file is corrupted.\n");
goto cleanup; goto cleanup;
} }
output = fopen(output_path, "wb"); /*
if (output == NULL || fseek(input, GERBERA_HEADER_SIZE, SEEK_SET) != 0) { * .
fprintf(stderr, "Could not create the output file.\n"); * .
goto cleanup; */
} gerbera_auth_update(&auth, buffer, count);
gerbera_cipher_init(&cipher, password, nonce);
remaining = plain_size; /* 처리한 만큼 남은 바이트 수를 줄입니다. */
while (remaining > 0) {
size_t wanted = remaining < sizeof(buffer) ? (size_t)remaining : sizeof(buffer);
count = fread(buffer, 1, wanted, input);
if (count != wanted) {
fprintf(stderr, "Could not read data for decryption.\n");
goto cleanup;
}
gerbera_cipher_apply(&cipher, buffer, count);
if (fwrite(buffer, 1, count, output) != count) {
fprintf(stderr, "Could not write the decrypted data.\n");
goto cleanup;
}
remaining -= count; remaining -= count;
} }
/*
* .
*/
if (fread(stored_tag, 1, sizeof(stored_tag), input) != sizeof(stored_tag)) {
/* 태그를 읽지 못하면 파일이 잘렸거나 형식이 깨진 것입니다. */
fprintf(stderr, "The authentication tag is missing.\n");
/* cleanup으로 이동합니다. */
goto cleanup;
}
/*
* .
*/
gerbera_auth_finish(&auth, calculated_tag);
/*
* .
*
* gerbera_constant_time_equal()
* .
*/
if (!gerbera_constant_time_equal(stored_tag, calculated_tag, sizeof(stored_tag))) {
/*
* ,
* ,
* .
*/
fprintf(stderr, "The password is incorrect or the file is corrupted.\n");
/* 인증 실패 시 출력 파일을 만들지 않고 종료합니다. */
goto cleanup;
}
/*
* .
* "wb" .
*/
output = fopen(output_path, "wb");
/*
* .
* .
*/
if (output == NULL || fseek(input, GERBERA_HEADER_SIZE, SEEK_SET) != 0) {
/*
* .
*
* ,
* fseek .
*/
fprintf(stderr, "Could not create the output file.\n");
/* cleanup으로 이동합니다. */
goto cleanup;
}
/*
* password와 nonce로
* .
*
* XOR .
*/
gerbera_cipher_init(&cipher, password, nonce);
/*
*
* remaining을 plain_size로 .
*/
remaining = plain_size;
/*
* .
*/
while (remaining > 0) {
/*
* .
* buffer보다 .
*/
size_t wanted = remaining < sizeof(buffer) ? (size_t)remaining : sizeof(buffer);
/*
* wanted .
*/
count = fread(buffer, 1, wanted, input);
/*
* wanted만큼 .
* .
*/
if (count != wanted) {
/* 복호화용 데이터를 읽지 못했다는 메시지입니다. */
fprintf(stderr, "Could not read data for decryption.\n");
/* cleanup으로 이동합니다. */
goto cleanup;
}
/*
* .
* XOR .
*/
gerbera_cipher_apply(&cipher, buffer, count);
/*
* .
*/
if (fwrite(buffer, 1, count, output) != count) {
/* 출력 파일 쓰기 실패 메시지입니다. */
fprintf(stderr, "Could not write the decrypted data.\n");
/* cleanup으로 이동합니다. */
goto cleanup;
}
/* 복호화한 만큼 남은 바이트 수를 줄입니다. */
remaining -= count;
}
/*
* , .
*/
result = 0; result = 0;
cleanup: cleanup:
/*
* input이 .
* NULL이면 .
*/
if (input != NULL) fclose(input); if (input != NULL) fclose(input);
/*
* output이 .
* fclose() .
*/
if (output != NULL && fclose(output) != 0) result = 1; if (output != NULL && fclose(output) != 0) result = 1;
/*
* .
* 0 , 1 .
*/
return result; return result;
} }

View file

@ -1,103 +1,336 @@
#include "gerbera.h" #include "gerbera.h" /* Gerbera 관련 타입, 상수, 함수 선언을 가져옵니다. */
#include <stdio.h> #include <stdio.h> /* FILE, fopen(), fread(), fwrite(), fprintf() 사용 */
#include <stdlib.h> #include <stdlib.h> /* srand(), rand(), remove() 사용 */
#include <time.h> #include <time.h> /* time(), clock() 사용 */
/*
* .
* .
*/
static const uint8_t magic[GERBERA_MAGIC_SIZE] = static const uint8_t magic[GERBERA_MAGIC_SIZE] =
{'M', 'I', 'Z', 'U', 'K', 'I', 0xF0, 0x9F, 0x8E, 0x80}; {'M', 'I', 'Z', 'U', 'K', 'I', 0xF0, 0x9F, 0x8E, 0x80};
/*
* 64 little-endian .
*
* value:
* 64
*
* bytes:
* 8
*/
static void encode_u64_le(uint8_t bytes[8], uint64_t value) static void encode_u64_le(uint8_t bytes[8], uint64_t value)
{ {
/* 0번 바이트부터 7번 바이트까지 순회하기 위한 변수입니다. */
unsigned n; unsigned n;
/* 64비트 값을 8비트씩 잘라서 낮은 바이트부터 저장합니다. */
for (n = 0; n < 8U; ++n) { for (n = 0; n < 8U; ++n) {
/*
* n * 8 .
* uint8_t로 8 .
*/
bytes[n] = (uint8_t)(value >> (n * 8U)); bytes[n] = (uint8_t)(value >> (n * 8U));
} }
} }
/*
* nonce를 .
*
* nonce는
* .
*/
static int random_nonce(uint8_t nonce[GERBERA_NONCE_SIZE]) static int random_nonce(uint8_t nonce[GERBERA_NONCE_SIZE])
{ {
/*
* srand() .
* static .
*/
static int initialized = 0; static int initialized = 0;
/*
* .
* srand() .
*/
if (!initialized) { if (!initialized) {
/*
* time(NULL):
* .
*
* clock():
* CPU .
*
* XOR해서 rand() .
*
* , rand()
* .
*/
srand((unsigned)time(NULL) ^ (unsigned)clock()); srand((unsigned)time(NULL) ^ (unsigned)clock());
/* 이제 시드 설정이 끝났다고 표시합니다. */
initialized = 1; initialized = 1;
} }
/* nonce 배열의 모든 바이트를 채웁니다. */
for (size_t i = 0; i < GERBERA_NONCE_SIZE; ++i) { for (size_t i = 0; i < GERBERA_NONCE_SIZE; ++i) {
/*
* rand() 8 .
* nonce의 .
*/
nonce[i] = (uint8_t)rand(); nonce[i] = (uint8_t)rand();
} }
/*
* API를
* 1 .
*/
return 1; return 1;
} }
int encrypt_file(const char *input_path, const char *output_path, /*
const char *password) * input_path에 ,
* output_path에 Gerbera .
*
* :
* 0 =
* 1 =
*/
int encrypt_file(const char *input_path, const char *output_path, const char *password)
{ {
/* 입력 파일 포인터입니다. 아직 열지 않았으므로 NULL로 초기화합니다. */
FILE *input = NULL; FILE *input = NULL;
/* 출력 파일 포인터입니다. 아직 열지 않았으므로 NULL로 초기화합니다. */
FILE *output = NULL; FILE *output = NULL;
/* 파일마다 새로 생성되는 nonce입니다. */
uint8_t nonce[GERBERA_NONCE_SIZE]; uint8_t nonce[GERBERA_NONCE_SIZE];
/* 원본 파일 크기를 little-endian으로 저장할 8바이트 배열입니다. */
uint8_t size_bytes[8]; uint8_t size_bytes[8];
/* 파일을 청크 단위로 읽고 암호화할 임시 버퍼입니다. */
uint8_t buffer[GERBERA_BUFFER_SIZE]; uint8_t buffer[GERBERA_BUFFER_SIZE];
/* 최종 인증 태그를 저장할 배열입니다. */
uint8_t tag[GERBERA_TAG_SIZE]; uint8_t tag[GERBERA_TAG_SIZE];
/* 스트림 암호 상태입니다. */
GerberaCipher cipher; GerberaCipher cipher;
/* 인증 태그 계산 상태입니다. */
GerberaAuth auth; GerberaAuth auth;
/* 입력 파일 크기를 저장합니다. ftell()이 long을 반환하므로 long을 사용합니다. */
long input_size; long input_size;
/* fread()로 실제 읽은 바이트 수를 저장합니다. */
size_t count; size_t count;
/*
* .
* 1 ,
* 0 .
*/
int result = 1; int result = 1;
/*
* .
* "rb" Windows에서도 .
*/
input = fopen(input_path, "rb"); input = fopen(input_path, "rb");
/*
* ,
* ,
* ftell() ,
* .
*
* .
*/
if (input == NULL || fseek(input, 0, SEEK_END) != 0 || if (input == NULL || fseek(input, 0, SEEK_END) != 0 ||
(input_size = ftell(input)) < 0 || fseek(input, 0, SEEK_SET) != 0) { (input_size = ftell(input)) < 0 || fseek(input, 0, SEEK_SET) != 0) {
/*
* .
*/
fprintf(stderr, "Could not open the input file or determine its size.\n"); fprintf(stderr, "Could not open the input file or determine its size.\n");
goto cleanup;
} /*
if (!random_nonce(nonce)) { * cleanup으로 .
fprintf(stderr, "Could not generate a secure random nonce.\n"); */
goto cleanup;
}
output = fopen(output_path, "wb");
if (output == NULL) {
fprintf(stderr, "Could not create the output file.\n");
goto cleanup; goto cleanup;
} }
/*
* nonce를 .
* nonce가 .
*/
if (!random_nonce(nonce)) {
/* nonce 생성 실패 메시지를 출력합니다. */
fprintf(stderr, "Could not generate a secure random nonce.\n");
/* cleanup으로 이동합니다. */
goto cleanup;
}
/*
* .
* "wb" .
*/
output = fopen(output_path, "wb");
/* 출력 파일 생성에 실패했는지 확인합니다. */
if (output == NULL) {
/* 오류 메시지를 출력합니다. */
fprintf(stderr, "Could not create the output file.\n");
/* cleanup으로 이동합니다. */
goto cleanup;
}
/*
* 8 little-endian .
* .
*/
encode_u64_le(size_bytes, (uint64_t)input_size); encode_u64_le(size_bytes, (uint64_t)input_size);
/*
* .
*
* :
* 1. magic : Gerbera
* 2. nonce :
* 3. size_bytes :
*/
if (fwrite(magic, 1, sizeof(magic), output) != sizeof(magic) || if (fwrite(magic, 1, sizeof(magic), output) != sizeof(magic) ||
fwrite(nonce, 1, sizeof(nonce), output) != sizeof(nonce) || fwrite(nonce, 1, sizeof(nonce), output) != sizeof(nonce) ||
fwrite(size_bytes, 1, sizeof(size_bytes), output) != sizeof(size_bytes)) { fwrite(size_bytes, 1, sizeof(size_bytes), output) != sizeof(size_bytes)) {
/* 헤더 쓰기에 실패한 경우 오류 메시지를 출력합니다. */
fprintf(stderr, "Could not write the encrypted file header.\n"); fprintf(stderr, "Could not write the encrypted file header.\n");
/* cleanup으로 이동합니다. */
goto cleanup; goto cleanup;
} }
/*
* .
* password와 nonce가 .
*/
gerbera_cipher_init(&cipher, password, nonce); gerbera_cipher_init(&cipher, password, nonce);
/*
* .
* .
*/
gerbera_auth_init(&auth, password, nonce); gerbera_auth_init(&auth, password, nonce);
/*
* magic .
* .
*/
gerbera_auth_update(&auth, magic, sizeof(magic)); gerbera_auth_update(&auth, magic, sizeof(magic));
/*
* nonce도 .
* nonce가 .
*/
gerbera_auth_update(&auth, nonce, sizeof(nonce)); gerbera_auth_update(&auth, nonce, sizeof(nonce));
/*
* .
* size_bytes가 .
*/
gerbera_auth_update(&auth, size_bytes, sizeof(size_bytes)); gerbera_auth_update(&auth, size_bytes, sizeof(size_bytes));
/*
* buffer .
* .
*/
while ((count = fread(buffer, 1, sizeof(buffer), input)) > 0) { while ((count = fread(buffer, 1, sizeof(buffer), input)) > 0) {
/*
* count .
* buffer .
*/
gerbera_cipher_apply(&cipher, buffer, count); gerbera_cipher_apply(&cipher, buffer, count);
/*
* .
* , .
*/
gerbera_auth_update(&auth, buffer, count); gerbera_auth_update(&auth, buffer, count);
/*
* .
*/
if (fwrite(buffer, 1, count, output) != count) { if (fwrite(buffer, 1, count, output) != count) {
/* 쓰기 실패 메시지를 출력합니다. */
fprintf(stderr, "Could not write the encrypted data.\n"); fprintf(stderr, "Could not write the encrypted data.\n");
/* cleanup으로 이동합니다. */
goto cleanup; goto cleanup;
} }
} }
/*
* fread() EOF인지,
* .
*/
if (ferror(input)) { if (ferror(input)) {
/* 읽기 오류 메시지를 출력합니다. */
fprintf(stderr, "An error occurred while reading the input file.\n"); fprintf(stderr, "An error occurred while reading the input file.\n");
/* cleanup으로 이동합니다. */
goto cleanup; goto cleanup;
} }
/*
* .
*
* :
* magic
* nonce
* size_bytes
*
*/
gerbera_auth_finish(&auth, tag); gerbera_auth_finish(&auth, tag);
/*
* .
* .
*/
if (fwrite(tag, 1, sizeof(tag), output) != sizeof(tag)) { if (fwrite(tag, 1, sizeof(tag), output) != sizeof(tag)) {
/* 태그 쓰기 실패 메시지를 출력합니다. */
fprintf(stderr, "Could not write the authentication tag.\n"); fprintf(stderr, "Could not write the authentication tag.\n");
/* cleanup으로 이동합니다. */
goto cleanup; goto cleanup;
} }
/*
* , , .
*/
result = 0; result = 0;
cleanup: cleanup:
/*
* input이 .
* NULL이면 .
*/
if (input != NULL) fclose(input); if (input != NULL) fclose(input);
/*
* output이 .
* fclose() .
*/
if (output != NULL && fclose(output) != 0) result = 1; if (output != NULL && fclose(output) != 0) result = 1;
/*
* output_path를 .
* .
*/
if (result != 0) remove(output_path); if (result != 0) remove(output_path);
/* 최종 결과를 반환합니다. */
return result; return result;
} }

View file

@ -1,10 +1,20 @@
#include "gerbera.h" #include "gerbera.h" /* encrypt_file(), decrypt_file() 선언을 가져옵니다. */
#include <stdio.h> #include <stdio.h> /* printf(), fprintf(), fgets(), stderr, stdin 사용 */
#include <string.h> #include <string.h> /* strcmp(), strcspn() 사용 */
/*
* .
*
* program:
* argv[0] .
*/
static void print_usage(const char *program) static void print_usage(const char *program)
{ {
/*
* (stdout) (stderr) .
* .
*/
fprintf(stderr, fprintf(stderr,
"Usage:\n" "Usage:\n"
" %s encrypt <input file> <output file> <password>\n" " %s encrypt <input file> <output file> <password>\n"
@ -12,80 +22,234 @@ static void print_usage(const char *program)
program, program); program, program);
} }
/*
* .
*
* mode, input, output, password를 .
*
* :
* 0 =
* 1 = /
* 2 =
*/
static int interactive_mode(void) static int interactive_mode(void)
{ {
/*
* .
* "encrypt" "decrypt" .
*/
char mode[16]; char mode[16];
/*
* .
* fgets() .
*/
char input[1024]; char input[1024];
/*
* .
*/
char output[1024]; char output[1024];
/*
* .
*
* :
* .
* echo를 .
*/
char password[1024]; char password[1024];
/* 사용자에게 작업 모드를 입력하라고 안내합니다. */
printf("Mode (encrypt/decrypt): "); printf("Mode (encrypt/decrypt): ");
if (!fgets(mode, sizeof(mode), stdin))
return 1;
/*
* mode .
* 1 .
*/
if (!fgets(mode, sizeof(mode), stdin)) return 1;
/* 사용자에게 입력 파일 경로를 입력하라고 안내합니다. */
printf("Input file: "); printf("Input file: ");
if (!fgets(input, sizeof(input), stdin))
return 1;
/*
* .
* 1 .
*/
if (!fgets(input, sizeof(input), stdin)) return 1;
/* 사용자에게 출력 파일 경로를 입력하라고 안내합니다. */
printf("Output file: "); printf("Output file: ");
if (!fgets(output, sizeof(output), stdin))
return 1;
/*
* .
* 1 .
*/
if (!fgets(output, sizeof(output), stdin)) return 1;
/* 사용자에게 비밀번호를 입력하라고 안내합니다. */
printf("Password: "); printf("Password: ");
if (!fgets(password, sizeof(password), stdin))
return 1;
/*
* .
* 1 .
*/
if (!fgets(password, sizeof(password), stdin)) return 1;
/*
* fgets() .
* strcspn(mode, "\r\n") mode에서 \r \n이 .
* '\0' .
*/
mode[strcspn(mode, "\r\n")] = '\0'; mode[strcspn(mode, "\r\n")] = '\0';
/* input 문자열 끝의 줄바꿈을 제거합니다. */
input[strcspn(input, "\r\n")] = '\0'; input[strcspn(input, "\r\n")] = '\0';
/* output 문자열 끝의 줄바꿈을 제거합니다. */
output[strcspn(output, "\r\n")] = '\0'; output[strcspn(output, "\r\n")] = '\0';
/* password 문자열 끝의 줄바꿈을 제거합니다. */
password[strcspn(password, "\r\n")] = '\0'; password[strcspn(password, "\r\n")] = '\0';
/*
* .
* .
*/
if (password[0] == '\0') { if (password[0] == '\0') {
/* 빈 비밀번호 오류 메시지를 출력합니다. */
fprintf(stderr, "The password cannot be empty.\n"); fprintf(stderr, "The password cannot be empty.\n");
/* 사용법 오류에 가까우므로 2를 반환합니다. */
return 2; return 2;
} }
/*
* .
* .
*/
if (strcmp(input, output) == 0) { if (strcmp(input, output) == 0) {
/* 입력과 출력 경로가 같다는 오류 메시지를 출력합니다. */
fprintf(stderr, "The paths of the input and output files cannot be the same.\n"); fprintf(stderr, "The paths of the input and output files cannot be the same.\n");
/* 잘못된 사용이므로 2를 반환합니다. */
return 2; return 2;
} }
/*
* mode가 "encrypt" .
* encrypt_file() .
*/
if (strcmp(mode, "encrypt") == 0) if (strcmp(mode, "encrypt") == 0)
return encrypt_file(input, output, password); return encrypt_file(input, output, password);
/*
* mode가 "decrypt" .
* decrypt_file() .
*/
if (strcmp(mode, "decrypt") == 0) if (strcmp(mode, "decrypt") == 0)
return decrypt_file(input, output, password); return decrypt_file(input, output, password);
/*
* encrypt도 decrypt도 .
*/
fprintf(stderr, "Unknown mode. Use 'encrypt' or 'decrypt'.\n"); fprintf(stderr, "Unknown mode. Use 'encrypt' or 'decrypt'.\n");
/* 잘못된 사용이므로 2를 반환합니다. */
return 2; return 2;
} }
/*
* .
*
* argc:
* .
*
* argv:
* .
*
* :
* ./gerbera encrypt input.txt output.gerbera password
* ./gerbera decrypt output.gerbera input.txt password
*/
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
/*
* argc가 1 .
* .
*/
if (argc == 1) if (argc == 1)
return interactive_mode(); return interactive_mode();
/*
* 5 .
*
* argv[0] =
* argv[1] = encrypt decrypt
* argv[2] =
* argv[3] =
* argv[4] =
*/
if (argc != 5) { if (argc != 5) {
/* 사용법을 출력합니다. */
print_usage(argv[0]); print_usage(argv[0]);
/* 인자 개수가 잘못되었으므로 2를 반환합니다. */
return 2; return 2;
} }
/*
* argv[4] .
* .
*/
if (argv[4][0] == '\0') { if (argv[4][0] == '\0') {
/* 빈 비밀번호 오류 메시지를 출력합니다. */
fprintf(stderr, "The password cannot be empty.\n"); fprintf(stderr, "The password cannot be empty.\n");
/* 잘못된 사용이므로 2를 반환합니다. */
return 2; return 2;
} }
/*
* argv[2] ,
* argv[3] .
*
* .
*/
if (strcmp(argv[2], argv[3]) == 0) { if (strcmp(argv[2], argv[3]) == 0) {
/* 입력과 출력 경로가 같다는 오류 메시지를 출력합니다. */
fprintf(stderr, "The paths of the input and output files cannot be the same.\n"); fprintf(stderr, "The paths of the input and output files cannot be the same.\n");
/* 잘못된 사용이므로 2를 반환합니다. */
return 2; return 2;
} }
/*
* "encrypt" .
*
* argv[2] =
* argv[3] =
* argv[4] =
*/
if (strcmp(argv[1], "encrypt") == 0) if (strcmp(argv[1], "encrypt") == 0)
return encrypt_file(argv[2], argv[3], argv[4]); return encrypt_file(argv[2], argv[3], argv[4]);
/*
* "decrypt" .
*
* argv[2] =
* argv[3] =
* argv[4] =
*/
if (strcmp(argv[1], "decrypt") == 0) if (strcmp(argv[1], "decrypt") == 0)
return decrypt_file(argv[2], argv[3], argv[4]); return decrypt_file(argv[2], argv[3], argv[4]);
/*
* encrypt도 decrypt도 .
* .
*/
print_usage(argv[0]); print_usage(argv[0]);
/* 잘못된 사용이므로 2를 반환합니다. */
return 2; return 2;
} }