wow
This commit is contained in:
parent
1a6bfc0925
commit
303192b198
9 changed files with 1154 additions and 34 deletions
113
.gitignore
vendored
113
.gitignore
vendored
|
|
@ -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
|
||||||
8
Makefile
8
Makefile
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
311
src/cipher.c
311
src/cipher.c
|
|
@ -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;
|
||||||
}
|
}
|
||||||
335
src/decrypt.c
335
src/decrypt.c
|
|
@ -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;
|
||||||
}
|
}
|
||||||
263
src/encrypt.c
263
src/encrypt.c
|
|
@ -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;
|
||||||
}
|
}
|
||||||
186
src/main.c
186
src/main.c
|
|
@ -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;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue