728x90

fillInStackTrace 정의

public Throwable fillInStackTrace()

 

  • 이 메서드는 Throwable 클래스에 정의되어 있으며, 모든 예외 클래스(Exception, Error)의 부모 클래스이기 때문에 예외 관련 객체에서 사용할 수 있습니다.
  • fillInStackTrace()를 호출하면 현재 위치에서의 스택 트레이스를 다시 기록합니다.
  • 일반적으로 예외를 재사용하거나, 예외를 다른 형태로 변환할 때 사용됩니다.
public class Example {
    public static void main(String[] args) {
        try {
            throwException();
        } catch (Exception e) {
            e.fillInStackTrace(); // 현재 스택 트레이스로 덮어씀
            e.printStackTrace();  // 덮어쓴 스택 트레이스를 출력
        }
    }

    static void throwException() throws Exception {
        throw new Exception("예외 발생!");
    }
}

사용 시 주의사항

  • fillInStackTrace()는 비용이 비쌀 수 있음 (스택 정보를 새로 생성).
  • 대부분의 경우, 이 메서드를 직접 호출할 필요는 없습니다.
  • 예외를 wrap할 때나, 커스텀 예외 처리에서 스택 트레이스를 재설정하고 싶을 때 사용됩니다.

 

fillInStackTrace를 통하여 스택트레이스를 제거하는 방법

class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }

    @Override
    public synchronized Throwable fillInStackTrace() {
        return this; // 스택 트레이스 무시
    }
}
728x90

'Programming > JAVA' 카테고리의 다른 글

JAVA JCA(Java Cryptography Architecture)의 개념  (0) 2025.04.21
728x90

 

Open Quantum Safe 프로젝트 개요

배경 및 프로젝트 목적

Open Quantum Safe(OQS) 프로젝트는 양자 내성 암호(quantum-resistant cryptography)로의 전환을 지원하기 위해 시작된 오픈 소스 프로젝트입니다. 양자 컴퓨팅 기술의 발전으로 기존의 RSA, ECC 등 공개키 암호가 슈어(Shor)의 알고리즘 등에 의해 손쉽게 깨질 수 있는 위험성이 대두되었고, “Y2Q”(또는 Q-Day)라 불리는 시점에 대비해야 한다는 필요성이 제기되었습니다. 특히 “오늘 저장해서 훗날 풀기(harvest now, decrypt later)”와 같은 공격 시나리오가 우려되는데, 이는 현재 안전하다고 여겨지는 암호 통신을 도감청·저장해 두었다가 미래의 양자 컴퓨터로 복호화하는 방식입니다. 이러한 배경에서 OQS 프로젝트는 학계 및 업계와 협력하여 양자 내성 암호 알고리즘의 개발과 프로토타이핑을 촉진함으로써, 향후 양자 컴퓨팅 시대에도 안전한 보안 통신을 구현하는 것을 목표로 하고 있습니다.

추진 기관과 출범 배경

OQS 프로젝트는 2014년 미셸 모스카(Michele Mosca)와 더글라스 스티빌라(Douglas Stebila)에 의해 연구 프로젝트로 설립되었으며, 이후 다수의 기여자와 후원자의 도움을 받아 성장해 왔습니다. 2024년에는 OQS 프로젝트가 리눅스 재단(Linux Foundation)에 합류하여, 해당 재단의 Post-Quantum Cryptography Alliance 출범 프로젝트 중 하나가 되었습니다. 이로써 OQS는 보다 광범위한 산업계·학계의 지원을 받으며 발전하고 있으며, 현재 기술 지침은 OQS 기술 운영 위원회(Technical Steering Committee)에 의해 관리되고 있습니다. 초기 단계부터 OQS는 아마존 웹 서비스(AWS), 캐나다 사이버 보안 센터, 시스코(Cisco), VeriSign 등 여러 기관의 후원을 받아왔고, 캐나다 및 호주 등지의 연구지원 기금을 통해 일부 구성 요소 개발을 진행하기도 했습니다.

주요 활동 및 구성 요소

OQS 프로젝트는 크게 두 가지 축으로 활동합니다:

  • liboqs 라이브러리 개발: liboqs는 양자 내성 암호 알고리즘들을 모아 제공하는 오픈 소스 C 라이브러리로, 키 캡슐화 메커니즘(KEM)과 디지털 서명 알고리즘 구현체들을 다수 포함하고 있습니다. 이 라이브러리는 다양한 최신 포스트 양자 알고리즘들을 한데 통합하여 일관된 API로 제공하며, 알고리즘 간 성능 비교를 위한 테스트 하네스와 벤치마크 도구도 포함하고 있습니다. liboqs에 구현된 알고리즘들은 주로 미국 NIST의 포스트 양자 암호 표준화 프로젝트에 제출된 공개 구현 코드를 기반으로 하고 있으며, NIST 표준화 결과에 따라 격자 기반, 코드 기반, 해시 기반 등 다양한 수학적 난제에 근거한 스킴들을 지원합니다. 예를 들어, 2022~2024년 NIST 최종 라운드에서 선정된 CRYSTALS-Kyber (격자 기반 KEM), CRYSTALS-Dilithium (격자 기반 서명), Falcon (격자 기반 서명), SPHINCS+ (해시 기반 서명) 등이 포함되었으며, 그 외에도 Classic McEliece(코드 기반 KEM), BIKE(코드 기반 KEM), HQC(코드 기반 KEM), FrodoKEM(격자 기반 KEM), NTRU 계열(격자 기반) 등 다양한 알고리즘을 실험적으로 지원합니다. 이러한 알고리즘 모음과 공용 API를 통해 개발자와 연구자들은 하나의 인터페이스로 여러 후보 암호들을 교체해가며 테스트할 수 있습니다.
  • 암호 프로토콜 통합 및 실험: OQS는 liboqs에서 제공하는 알고리즘을 실제 애플리케이션과 통신 프로토콜에 접목하는 프로토타입 통합 작업을 수행합니다. 대표적으로 TLS/SSL 구현체인 OpenSSL BoringSSL의 포크(fork)를 만들어, 여기에 후보 PQC 알고리즘을 적용한 키 교환 및 인증서 검증 절차를 통합했습니다. OQS-OpenSSL (1.1.1 기반) 및 OpenSSL 3에 대한 OQS 제공자(provider), 그리고 BoringSSL 포크 등이 개발되어 TLS 1.3 핸드셰이크에 양자 내성 하이브리드 키 교환이나 PQ 서명 알고리즘을 적용한 실험이 가능해졌습니다. 마찬가지로 SSH 프로토콜에서도 OpenSSH를 포크한 OQS-OpenSSH를 통해 PQ 및 하이브리드 키 교환을 구현하여 시범 운영하고 있습니다. 이외에도 X.509 인증서, VPN, MQTT 등 다양한 보안 프로토콜에 양자 내성 알고리즘을 접목한 예제들과 데모를 제공함으로써, 실제 환경에서의 호환성과 성능을 시험하고 개발자들이 쉽게 실험해볼 수 있도록 지원합니다. 다만 이러한 통합 버전들은 어디까지나 프로토타이핑  연구 목적의 레퍼런스 구현으로 제공되는 것이며, 상용 환경에서 바로 사용하기에는 성숙도가 부족하다는 점을 명시하고 있습니다 . 실제 배포시에는 전통 암호와 PQC의 혼합(hybrid)사용 등을 통해 점진적으로 전환하는 것이 권장되며, OQS 측도 보안 한계와 위험성을 프로젝트 FAQ와 문서를 통해 투명하게 공유하고 있습니다.

사용하는 주요 기술 (liboqs 등)

liboqs는 OQS 프로젝트의 핵심 기술 산출물로서, 다음과 같은 특징을 지닙니다:

  • 오픈 소스 및 다중 플랫폼: MIT 라이선스로 공개된 liboqs는 리눅스, macOS, Windows 등 여러 운영체제와 x86_64, ARM 등의 아키텍처에서 동작하도록 설계되었습니다. 크로스 컴파일 도구체인을 통해 임베디드 등 기타 플랫폼용 빌드도 지원하며, C99 준수 API로 구현됩니다.
  • 공용 API 제공: KEM 알고리즘들과 전자서명 알고리즘에 대한 통일된 함수 호출 인터페이스를 제공하여, 응용 프로그램이 특정 알고리즘에 종속되지 않고도 쉽게 교체 실험을 할 수 있습니다. 이 API 설계는 NIST 및 SUPERCOP의 표준 인터페이스를 참고하여 만들어졌으며, 추가적인 래퍼와 데이터 구조를 통해 사용성을 높였습니다.
  • 다언어 래퍼: C 라이브러리인 liboqs의 기능을 C++(oqs-cpp), Python(oqs-python), Go(oqs-go), Rust(oqs-sys) 등 다양한 언어 환경에서 사용할 수 있도록 래퍼(wrapper) 프로젝트도 제공됩니다. 이를 통해 폭넓은 개발자 커뮤니티가 자신이 선호하는 언어로 양자 내성 암호 기술을 시험해볼 수 있습니다.
  • 알고리즘 구현 및 관리: liboqs에 포함된 알고리즘 구현체들은 NIST PQC 경연에 참가한 팀들이 공개한 레퍼런스 구현 또는 최적화 구현을 그대로 가져오거나, PQClean 등의 프로젝트를 통해 표준화된 형태로 통합된 것입니다. 따라서 학계 최신 구현을 폭넓게 아우르며, 알고리즘별로 성능 벤치마크와 테스트를 수행해 신뢰성을 확인합니다. OQS는 특정 알고리즘의 “승자”를 미리 결정하지 않으며, NIST 표준화 결과와 연구 동향에 따라 지원 알고리즘 목록을 조정합니다. 예컨대, 4라운드 표준화에서 탈락하거나 보안 약점이 발견된 알고리즘(예: SIKE의 기반인 SIDH의 공격 사례)은 목록에서 제거하고, 새로운 후보나 표준 채택 알고리즘은 추가하는 식입니다. 이러한 유연성 개방성은 향후 더 강력한 양자 공격이나 추가 알고리즘 발전이 있을 경우에도 지속적으로 대응할 수 있게 해줍니다.

양자 컴퓨팅 시대를 대비한 보안적 의미

OQS 프로젝트의 등장은 양자 컴퓨팅 시대에 대비한 선제적 보안 강화 노력이라는 점에서 중요한 의미를 가집니다. 오늘날 양자 컴퓨터의 성능은 아직 제한적이어서 실제로 현행 암호를 깨지는 못하지만, 암호 표준 교체에는 오랜 시간이 소요되기 때문에 미리 대비책을 마련해야 합니다. 모스카의 정리(Mosca’s theorem)에서도 언급되듯이, 데이터의 보유 기간과 알고리즘 전환 기간, 양자 컴퓨터 개발 속도를 고려하면 지금 이 순간부터 준비를 시작해야 안전하다고 합니다. OQS는 이러한 대비를 위해 오픈 소스 커뮤니티 주도로 누구나 PQC 알고리즘을 시험하고 기존 시스템에 통합해볼 수 있는 공개된 장터(테스트베드) 역할을 합니다.

또한 OQS 프로젝트는 표준화 기관(NIST 등)의 노력과 보조를 맞춰 움직이므로, 산업계가 혼란을 최소화하면서 양자 내성 암호로 이행하도록 돕고 있습니다. 예를 들어 NIST가 2022~2024년에 걸쳐 선정한 초기 PQC 표준 알고리즘들을 OQS의 도구체인에 신속히 반영함으로써, 웹 브라우저, TLS 라이브러리, VPN 소프트웨어 등에서 일찍이 양자 안전 기능을 시험 구축해볼 수 있었습니다. 일부 기술 선도 기업들은 이미 iMessage나 Signal과 같은 메시징 시스템, Cloudflare의 TLS 트래픽 등에 PQC를 도입하거나 시범운용하고 있는데, 이러한 실험들이 가능하도록 OQS가 공통 플랫폼을 제공한 셈입니다. 요약하면, Open Quantum Safe 프로젝트는 향후 도래할 양자 컴퓨팅 위협에 대응하여 암호 인프라의 안전한 업그레이드 경로를 마련해주는 핵심적인 오픈 소스 이니셔티브이며, 정부·기업·학계에 정보 공유와 협력의 장을 제공함으로써 모두의 보안 수준을 높이는 데 기여하고 있습니다.

728x90
728x90

Windows: DLL에서 함수/심볼 Export하기

Windows의 DLL에서는 내보낼 함수에 대해 명시적으로 export 지시를 해주어야 합니다. 기본적으로 DLL에 정의된 함수들은 외부에 자동 공개되지 않으며, 외부에 제공하려는 함수들은 export 대상임을 표시해야 합니다. 이를 위해 Microsoft 컴파일러에서는 __declspec(dllexport) 키워드를 사용합니다. 반대로, DLL에 들어있는 함수를 외부 애플리케이션에서 사용할 때는 __declspec(dllimport)로 가져오는(import) 함수임을 표시합니다. 예를 들어, DLL을 만들 때 헤더에서 함수를 다음과 같이 선언합니다:

// mylib.h (DLL을 빌드할 때)
__declspec(dllexport) int add(int a, int b);

위와 같이 선언하고 DLL을 빌드하면 add 함수가 DLL의 외부 Export 심볼 테이블에 등록되어 다른 프로그램에서 접근할 수 있게 됩니다. DLL을 사용하는 애플리케이션 쪽에서는 해당 함수를 선언할 때 import 지시어를 붙여주는 것이 일반적입니다:

// 사용 측 애플리케이션 코드
__declspec(dllimport) int add(int a, int b);  // DLL에서 가져올 함수 선언
...
int result = add(2, 3);  // DLL의 함수를 호출

dllimport로 표시하면 컴파일러가 DLL의 외부 함수임을 인지하여 적절한 호출 코드를 생성합니다. 참고로, 함수 사용 측에서 dllimport를 생략해도 동작은 가능하지만, 명시하는 것이 올바른 사용법이며 컴파일러 최적화에 유리합니다.

헤더 파일 구성: DLL을 만들 때와 사용할 때 서로 다른 선언(dllexport vs dllimport)이 필요하지만, 이를 위해 헤더 파일을 두 벌로 나누지 않고 보통 매크로를 이용해 하나의 헤더로 관리합니다. 예를 들어 DLL을 빌드할 때 특정 매크로(MYLIB_EXPORTS 등)를 정의해 두면, 헤더에서 해당 매크로가 정의된 경우 dllexport를, 아닌 경우 dllimport를 붙이도록 구현합니다. 코드 예시는 다음과 같습니다:

// mylib_export.h - Windows DLL용 매크로 정의
#ifdef MYLIB_EXPORTS        // DLL 빌드 시에는 이 매크로를 정의
  #define MYLIB_API __declspec(dllexport)
#else                       // DLL을 사용하는 경우
  #define MYLIB_API __declspec(dllimport)
#endif

// mylib.h - 함수 선언부
#include "mylib_export.h"
MYLIB_API int add(int a, int b);

위와 같이 하면 DLL을 빌드할 때는 MYLIB_EXPORTS를 정의하여 컴파일하고, DLL 사용자 측에서는 해당 매크로를 정의하지 않으면 자동으로 dllimport가 적용된 선언을 사용하게 됩니다. 이러한 방식으로 하나의 헤더 파일로 DLL 내보내기/가져오기를 관리할 수 있습니다.

 

 

컴파일 및 링크 옵션 (Windows):

  • 컴파일 시 정의: DLL 빌드 시 MYLIB_EXPORTS와 같은 매크로를 정의(/D MYLIB_EXPORTS 또는 -DMYLIB_EXPORTS)해야 합니다. Visual C++의 DLL 프로젝트를 생성하면 보통 프로젝트 이름에 _EXPORTS 매크로가 자동 정의되어 위와 같은 조건을 만족하도록 해줍니다.
  • DLL 생성 플래그: MSVC에서는 /LD 옵션을 사용하여 DLL을 생성합니다. 예를 들어 명령행에서 컴파일하면:위 명령은 mylib.dll DLL을 생성하고, 함께 mylib.lib 라는 import 라이브러리도 생성합니다. .lib import 라이브러리는 DLL의 함수들을 링커가 연결할 수 있도록 하는 정보가 담긴 파일로, 응용 프로그램을 빌드할 때 필요합니다. 런타임에는 .dll 파일만 있으면 되지만, 컴파일 단계에서 링커는 .lib를 통해 외부 함수 참조를 해결합니다
  • cl /LD /D MYLIB_EXPORTS mylib.c /Fe:mylib.dll
  • MinGW(gcc) 사용 시: GCC 컴파일러를 사용해 Windows DLL을 만드는 경우에도 소스 코드에서는 동일하게 __declspec(dllexport)를 사용하며, -shared 옵션으로 DLL을 생성합니다. 예를 들어:(GCC로 DLL을 만들 때도 dlltool 등을 사용하여 import 라이브러리(.a)를 생성할 수 있습니다. 하지만 보통 MSVC 환경에서 .lib를 이용해 링크하거나, 필요 시 GCC의 -Wl,--out-implib 옵션으로 import 라이브러리를 얻을 수도 있습니다.)
  • gcc -shared -o mylib.dll mylib.c -DMYLIB_EXPORTS

Linux: 공유 객체(.so)에서 함수/심볼 Export하기

Linux의 공유 라이브러리(.so)는 Windows와 달리 별도의 export 지시어 없이도 전역 심볼은 기본적으로 외부에 공개됩니다. GCC를 사용해 -shared 옵션으로 컴파일하면, static이 아닌 전역 함수들은 자동으로 .so의 심볼 테이블에 노출되어 다른 프로그램이 사용할 수 있습니다. 예를 들어 간단한 C 소스를 공유 라이브러리로 빌드하면, 특별한 지시어 없이도 그 함수들은 외부에서 extern 선언으로 사용 가능합니다.

gcc -fPIC -shared -o libmylib.so mylib.c    # libmylib.so 생성 (기본적으로 모든 전역함수 export)

 

위 명령처럼 -fPIC (Position Independent Code) 옵션을 주어 오브젝트 파일을 생성하고, -shared 옵션으로 .so 파일을 링크합니다. 이렇게 하면 mylib.c에 정의된 전역 함수들이 libmylib.so의 공개 API로서 노출됩니다.

심볼 가시성 제어 (선택 사항): 기본 동작은 모든 전역 심볼 공개이지만, 원하지 않는 내부 함수까지 노출되지 않도록 제어하는 것이 권장됩니다. GCC에서는 -fvisibility 옵션으로 기본 심볼 가시성을 조절할 수 있습니다. 기본값 -fvisibility=default에서는 모든 심볼이 공개되지만, 이를 **-fvisibility=hidden**으로 변경하면 기본적으로 모든 심볼을 숨기고, 특정 함수에 한해 __attribute__((visibility("default"))) 속성을 부여하여 선택적으로 공개할 수 있습니다.

즉, 라이브러리를 컴파일할 때 -fvisibility=hidden 플래그를 주고, 헤더 파일에서 외부 API로 공개할 함수 선언에만 아래와 같은 속성(attribute)을 붙입니다:

// mylib.h (Linux에서 가시성 제어 예시)
#ifdef __GNUC__
  #define MYLIB_API __attribute__((visibility("default")))
#else
  #define MYLIB_API
#endif

MYLIB_API int add(int a, int b);   // 외부에 공개할 함수

 

이렇게 하면 add 함수만 공유 라이브러리의 외부 심볼로 노출되고, 그 외의 전역 함수들은 숨겨지게 됩니다. 이 기법을 사용하면 필요한 API만 노출할 수 있을 뿐 아니라, 바이너리 크기 감소, 링크 충돌 회피, 로드 시간 단축 등의 이점이 있습니다. 숨겨진 심볼은 외부에서 참조할 수 없으므로, 다른 라이브러리와의 이름 충돌을 방지하고 불필요한 심볼로 인한 오버헤드를 줄일 수 있습니다

헤더 파일 매크로 처리: 크로스 플랫폼을 고려하여, Windows와 Linux에서 모두 사용할 수 있는 매크로를 정의할 수 있습니다. 예를 들어 하나의 MYLIB_API 매크로를 만들어 Windows에서는 __declspec(dllexport/dllimport)을, Linux에서는 __attribute__((visibility("default")))로 매핑하면 동일한 헤더 코드로 양쪽을 처리할 수 있습니다. Windows에서는 빌드 시 export 매크로를 활성화하고, Linux에서는 컴파일러 특성만 부여하는 형태입니다.

크로스플랫폼 예제 코드 및 빌드

아래에는 Windows와 Linux에서 모두 사용 가능한 형태의 공유 라이브러리 예제를 제공합니다. 간단한 add 함수를 라이브러리 API로 공개하는 코드이며, 매크로로 플랫폼별 export 규칙을 처리합니다:

/* mylib.h - 공용 헤더 (Windows & Linux) */
#if defined(_WIN32) || defined(_WIN64)
  #ifdef MYLIB_EXPORTS
    #define MYLIB_API __declspec(dllexport)
  #else
    #define MYLIB_API __declspec(dllimport)
  #endif
#elif defined(__GNUC__) && __GNUC__ >= 4
  #define MYLIB_API __attribute__((visibility("default")))
#else
  #define MYLIB_API
#endif

// 외부에 공개할 함수 선언
MYLIB_API int add(int a, int b);

#ifdef __cplusplus
extern "C" {
#endif

// ... (다른 공개 함수들 선언 가능)

#ifdef __cplusplus
}
#endif

/* mylib.c - 구현 파일 */
#include "mylib.h"

MYLIB_API int add(int a, int b) {
    return a + b;
}

// (내부 전용 함수는 static으로 정의하거나, Linux의 경우 visibility("hidden") 등을 활용)
  • MYLIB_EXPORTS 매크로는 라이브러리를 빌드할 때 정의합니다. 이렇게 하면 Windows에서는 MYLIB_API가 __declspec(dllexport)로 설정되어 함수 add가 DLL의 export 심볼로 추가되고, Linux에서는 MYLIB_API가 visibility("default")로 설정되어 (혹은 -fvisibility=hidden 빌드 시) 해당 함수가 공개 심볼이 됩니다. 라이브러리를 사용하는 코드에서는 MYLIB_EXPORTS를 정의하지 않고 헤더를 포함하므로 Windows의 경우 dllimport로 선언되며, Linux의 경우 일반 선언과 다르지 않게 동작합니다.

빌드 방법:

  • Windows (MSVC): 위 코드로 DLL을 만들려면, 컴파일 단계에서 MYLIB_EXPORTS를 정의하고 /LD 옵션으로 DLL을 생성합니다. 예를 들어 명령어:이 명령을 실행하면 mylib.dll과 import 라이브러리 mylib.lib가 생성됩니다. 생성된 DLL을 다른 프로젝트에서 사용할 때는 mylib.h를 포함하고 mylib.lib를 링크하면, add 함수를 호출할 수 있습니다.
  • cl /LD /D MYLIB_EXPORTS mylib.c /Fe:mylib.dll
  • Linux (GCC): 공유 객체(.so)를 만들 때는 포지션 독립 코드를 위해 -fPIC 옵션을 주고, 공유 라이브러리로 빌드하기 위해 -shared 옵션을 사용합니다. 예를 들어:이렇게 하면 libmylib.so가 생성되며, 기본 설정에서는 add 함수 등 전역 함수들이 모두 공개 심볼로 포함됩니다. 만약 내부 함수는 숨기고 add만 export하려면 컴파일시에 -fvisibility=hidden 플래그를 추가하고, 위 헤더에서 정의한 대로 MYLIB_API 속성을 붙인 함수만 공개됩니다. 생성된 libmylib.so는 표준 라이브러리 디렉토리에 두거나, 컴파일 시에 -L 옵션으로 경로를 지정하고 -lmylib로 링크하여 사용할 수 있습니다.
  • gcc -fPIC -shared -o libmylib.so mylib.c
728x90
728x90

 

소스 코드로 작성한 C 기반 공유 라이브러리에서 어떤 함수나 전역 변수가 외부로 export되고 있는지 확인하려면, 각각의 운영체제에 맞는 도구를 사용해야 합니다. Linux의 .so(ELF 형식)와 Windows의 .dll(PE 형식)은 바이너리 포맷이 다르므로, 이를 분석하는 도구도 달라집니다. 아래에서는 CLI 도구 GUI 도구를 활용하여 각 플랫폼별로 라이브러리 내보낸 심볼 목록을 확인하는 방법을 설명합니다. 또한 빌드 시에 필요한 옵션이나 유의사항도 함께 다룹니다.

Linux – 공유 라이브러리(.so)의 심볼 확인

Linux에서 공유 라이브러리(.so)에 export된 심볼(외부에 공개된 함수나 전역변수)이 무엇인지 확인하는 대표적 CLI 도구는 nm과 objdump, readelf 등이 있습니다. 이들은 ELF 파일의 심볼 테이블을 읽어와 어떤 심볼들이 정의되어 있고(export) 또는 참조되고(import) 있는지 보여줍니다.

nm 도구 사용 (CLI)

nm은 오브젝트 파일이나 라이브러리의 심볼 목록을 출력해주는 GNU Binutils 도구입니다. 기본적으로 nm을 그냥 실행하면 정적(symbol table) 심볼까지 모두 보여주는데, 공유 라이브러리의 export 심볼만 보려면 동적 심볼 테이블만 표시하도록 -D 옵션을 주는 것이 좋습니다. nm -D는 공유 라이브러리의 동적 심볼(다이나믹 심볼 테이블에 등재된 항목)만 표시하며, 이것이 곧 외부에 노출된 API 심볼과 외부 의존 심볼을 나타냅니다. 사용 예시는 다음과 같습니다.

$ nm -D libexample.so

 

위 명령의 출력에는 각 심볼의 주소(address), 타입(code/data/undefined 등), 이름(name)이 나옵니다. 예를 들어 어떤 라이브러리가 exported_function이라는 글로벌 함수와 exported_variable이라는 글로벌 변수를 내보내고, internal_function이라는 static 함수와 internal_variable이라는 static 변수를 내부적으로만 가지고 있다고 가정해보겠습니다. 이 경우 nm -D libexample.so의 출력 예시는 대략 다음과 같습니다:

00000000 T exported_function
00000000 D exported_variable
         U printf@GLIBC_2.2.5
  • T exported_function  T는 전역(Text) 심볼로서, 라이브러리 내부에 정의된 글로벌 함수임을 나타냅니다. 즉 이 함수가 외부로 export 되어 있음을 의미합니다
  • D exported_variable  D는 전역(Data) 심볼로서, 초기값이 있는 글로벌 변수가 export되었음을 나타냅니다
  • U printf@GLIBC_2.2.5  U는 정의되지 않은(Undefined) 외부 심볼로, 이 라이브러리가 사용하는 다른 라이브러리의 함수입니다 (여기서는 printf, GLIBC로부터 가져옴). 즉 import된 심볼을 뜻하며, export 심볼과 구분됩니다.

nm 출력의 심볼 타입 코드는 대소문자로 구분되며, export 여부와 섹션을 나타냅니다. 예를 들어 대문자 T, D, B 등은 전역 심볼(외부 공개)을 의미하고, 소문자 t, d, b 등은 로컬 심볼(내부)을 의미합니다. 주요 코드의 의미는 다음과 같습니다.

  • T (Text): 전역 함수 코드 심볼 (export된 함수)
  • t (text): 지역 함수 코드 심볼 (static 함수 등, 외부 비공개)
  • D (Data): 전역 초기화 데이터 심볼 (export된 전역 변수)
  • d (data): 지역 초기화 데이터 심볼 (파일 내부 static 변수 등)
  • B (BSS): 전역 초기화되지 않은 심볼 (export된 전역 변수, 초기값 없음)
  • b (bss): 지역 초기화되지 않은 심볼 (static 전역 변수, 초기값 없음)
  • U (Undefined): 정의되어 있지 않은 심볼 (다른 객체/라이브러리에서 제공되는 함수나 변수)

nm -D로 보면 외부에 노출된 심볼들만 나타나기 때문에, 내부 static 함수나 변수들은 출력되지 않습니다 (동적 심볼 테이블에 없으므로). 만약 nm을 -D 없이 실행하면 정적 심볼 테이블까지 포함하여 모든 심볼이 출력되는데, 이 경우 static 심볼들도 보입니다. 예를 들어 nm libexample.so (옵션 없이)의 출력에 internal_function이 소문자 t로 나타난다면, 이는 해당 함수가 내부(static) 심볼이라 외부로 export되지 않았음을 알 수 있습니다. 반면 nm -D 출력에는 internal_function이 없으므로 export되지 않은 것입니다.

참고: C++로 작성된 라이브러리의 경우 nm 출력 심볼 이름이 mangled name(이름 변환) 형태일 수 있습니다. 이때 nm --demangle (또는 nm -C) 옵션이나 c++filt 도구를 사용하면 사람이 읽기 쉬운 원래의 함수 이름으로 풀어줍니다. C 코드 또는 extern "C"로 선언된 함수는 이름이 그대로 출력됩니다.

nm은 맥 OS 등 다른 Unix 계열에서도 사용 가능합니다. Mac의 .dylib에서는 nm -gU 등을 사용하지만, 여기서는 Linux ELF에 집중합니다.

objdump 도구 사용 (CLI)

objdump는 바이너리의 다양한 정보를 표시할 수 있는 만능 도구입니다. 심볼 목록을 보려면 objdump에 적절한 옵션을 주면 됩니다. 두 가지 방식이 있습니다.

  • objdump -t libexample.so : 정적 심볼 테이블(symbol table)의 모든 심볼을 표시합니다 (nm 기본 출력과 유사). 여기엔 로컬 심볼까지 모두 나오지만, 보통 공유 라이브러리는 릴리즈 빌드시 불필요한 심볼을 strip하기 때문에 -t 출력이 없을 수도 있습니다.
  • objdump -T libexample.so : 동적 심볼 테이블을 표시합니다. -T는 ELF의 .dynsym 섹션을 덤프하며, 이는 nm -D와 마찬가지로 export된 심볼과 import된 외부 참조만 보여줍니다

예를 들어 objdump -T libexample.so의 출력은 다음과 같은 컬럼을 가집니다.

$ objdump -T libexample.so

00000000 g    DF .text  0000003d  Base    exported_function
00000000 g    DO .data  00000004  Base    exported_variable
...
  • 앞의 주소와 함께 g DF .text처럼 표시되는데, g는 글로벌, DF는 FUNC (함수), .text는 코드 섹션에 정의됨을 뜻합니다. 즉 exported_function이 전역 함수로 export되었음을 보여줍니다.
  • DO .data는 OBJECT (데이터 객체)를 의미하며 .data 섹션에 있는 전역 변수 심볼임을 나타냅니다. exported_variable이 전역 변수로 export되었다는 뜻입니다.
  • Base는 심볼 버전(version)이 없는 기본 심볼임을 나타냅니다 (라이브러리에 버전 스크립트를 사용한 경우 해당 심볼의 버전 정보가 표시될 수 있습니다).

이처럼 objdump -T는 nm -D와 유사한 정보를 제공하지만, 심볼의 크기나 섹션 같은 추가 정보도 보여줍니다. 만약 자세한 헤더와 섹션 정보까지 보고 싶다면 objdump -x libexample.so (파일 헤더와 섹션 헤더 등 모두 출력)로 전체 내용을 볼 수 있습니다

readelf 도구 사용 (CLI)

readelf는 ELF 파일 구조를 해석해주는 도구입니다. 심볼 정보를 보려면 readelf -s 옵션을 사용합니다. 기본적으로 readelf -s libexample.so는 심볼 테이블을 모두 보여주는데, 공유 라이브러리의 경우 정적 심볼 테이블(.symtab)과 동적 심볼 테이블(.dynsym)이 모두 있을 수 있습니다. 동적 심볼만 보려면 readelf --dyn-syms -s libexample.so 또는 줄여서 readelf -Ds libexample.so 명령을 사용할 수 있습니다 출력 형식은 nm보다 상세하여, 각 심볼의 인덱스, 값, 크기, 타입(Func/Object), 바인딩(Global/Local), 섹션, 이름 등이 테이블 형태로 나옵니다. Global 바인딩의 FUNC/OBJECT가 곧 export된 함수/변수이고, UND로 표시된 것은 undefined (import) 심볼입니다.

예를 들어 readelf -Ws libexample.so의 출력 일부는 다음과 같을 수 있습니다 (열을 간략화하여 표시):

   Num: Value    Size  Type    Bind   Vis      Ndx Name
   10: 0000111b  0x3d  FUNC    GLOBAL DEFAULT   13 exported_function
   11: 00004010  0x4   OBJECT  GLOBAL DEFAULT   25 exported_variable
   12: 00000000    0   FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5

여기서 Bind GLOBAL이고 Ndx (섹션 인덱스)가 UND가 아닌 항목들이 바로 이 라이브러리에서 제공하는 export 심볼입니다.

readelf의 출력은 기계적으로 정확하지만 다소 장황할 수 있으므로, 간단히는 nm이나 objdump 출력만으로도 충분합니다.

Linux에서의 빌드 옵션과 심볼 가시성

Linux의 공유 라이브러리는 별다른 지시 없이 컴파일하면 모든 전역 함수/변수가 기본적으로 export됩니다. 즉, static으로 선언하지 않은 함수는 특별히 숨기지 않는 한 동적 심볼 테이블에 나타나게 됩니다. 경우에 따라 내부 구현 함수까지 외부에 노출되는 것을 원하지 않을 수 있는데, 이런 불필요한 심볼 노출을 막기 위해 visibility를 제어할 수 있습니다. GCC에서는 -fvisibility=hidden 컴파일 옵션을 주면 기본적으로 모든 전역 심볼을 숨기고, 필요한 심볼만 선택적으로 export할 수 있습니다. 예를 들어 컴파일/링크 옵션에 -fvisibility=hidden을 추가하고, 외부에 공개해야 할 함수나 변수 선언에 __attribute__((visibility("default")))를 붙이면 해당 심볼만 외부로 공개되고 나머지는 숨겨집니다 .

이 기법을 사용하면 nm -D 출력에서 공개하지 않은 내부 함수들은 사라지고, 설계한 API만 export됩니다. (참고로, GCC 4.x 이후로는 라이브러리 제작 시 이 방법이 권장되어, 불필요한 심볼을 줄이고 DSO 로딩 시간을 개선하는 등 이점이 있습니다.) 예시:

// 헤더 파일 선언부
__attribute__((visibility("default"))) void public_func();
// 구현 파일
void public_func() { /* ... */ }       // 공개함수: default visibility
static void internal_util() { /* ... */ }  // 내부함수: hidden (static이거나 visibility hidden)

이렇게 빌드하면 nm -D에 public_func만 나타나고 internal_util은 나타나지 않게 됩니다. (동일 효과를 링커의 버전 스크립트로도 낼 수 있지만, 컴파일 속성을 사용하는 방법이 더 간편합니다.)

Windows – DLL(.dll)의 심볼 확인

Windows DLL에서는 어떤 함수들이 외부로 export되었는지 확인하는 방법으로 CLI 도구 GUI 도구를 모두 사용할 수 있습니다. Windows에서는 기본적으로 DLL을 빌드할 때 개발자가 export 대상을 지정해주지 않으면 아무 심볼도 export되지 않는다는 점이 Linux와 다릅니다. 따라서 Windows DLL의 내보낸 심볼 목록을 확인하는 것은 곧, 빌드 시 설정한 __declspec(dllexport)나 .def 파일에 의해 지정된 API가 잘 포함되었는지를 확인하는 과정입니다.

대표적인 CLI 도구로는 Visual Studio에 포함된 dumpbin이 있고, GUI 도구로는 Dependency Walker 등이 널리 사용됩니다.

dumpbin 도구 사용 (CLI)

Microsoft의 Visual C++ 툴체인에는 dumpbin이라는 실행 파일이 포함되어 있는데, 이를 이용하면 DLL(또는 LIB)의 정보를 덤프할 수 있습니다. Developer Command Prompt (개발자 명령 프롬프트)에서 dumpbin /EXPORTS 파일명.dll 명령을 실행하면 해당 DLL의 내보낸 심볼 목록을 볼 수 있습니다. 사용 순서는 다음과 같습니다.

  1. Visual Studio에서 개발자 명령 프롬프트를 엽니다 (VS2019/2022의 경우 Tools > Command Line > Developer Command Prompt 메뉴 등으로 실행).
  2. DLL 파일이 있는 디렉토리로 이동하여, 명령어 dumpbin /exports MyLibrary.dll를 실행합니다.
  3. 출력 결과를 확인합니다.

예를 들어 MyLibrary.dll에 대해 dumpbin /exports MyLibrary.dll을 실행하면 다음과 같은 형식의 출력이 나타납니다:

Dump of file MyLibrary.dll

File Type: DLL

  Section contains the following exports for MyLibrary.dll

    00000000 characteristics
    00000000 time date stamp
        0.00 version
        1 ordinal base
        3 number of functions
        3 number of names

    ordinal hint   RVA      name
          1    0   00001000 MyFunction
          2    1   00001030 MyVariable
          3    2   00002000 _DllMainCRTStartup@12

위 출력에서 중요한 부분은 마지막 표 형태입니다. 각 열의 의미는 다음과 같습니다.

  • ordinal: 내보낸 심볼의 서수(Ordinal) 값입니다. DLL에서 심볼마다 고유한 번호가 부여되며, import 라이브러리가 이 번호로 참조할 수도 있습니다. Ordinal base가 1이면 첫 번째 심볼의 ordinal이 1부터 시작함을 뜻합니다.
  • hint: 심볼 이름 검색을 빠르게 하기 위한 힌트 인덱스입니다. (보통 내부용이라 크게 신경쓰지 않아도 됩니다.)
  • RVA: 해당 심볼의 Relative Virtual Address (상대 가상 주소)로, DLL 로드 시 메모리에서의 오프셋 주소를 나타냅니다. 즉, DLL 시작을 기준으로 함수 코드가 있는 주소 값입니다.
  • name: 내보낸 심볼 이름입니다. 함수 이름 또는 변수 이름이 표시됩니다. C로 export된 함수는 그대로 이름이 나오지만, __stdcall 규약을 사용했다면 이름 뒤에 @<스택 바이트> 형식으로 표기되거나, C++ 함수라면 맹글링된 이름이 보일 수 있습니다. 예를 들어 _DllMainCRTStartup@12는 DLL 엔트리 포인트로, stdcall 규약에 따라 추가 장식이 된 이름입니다.

dumpbin /exports 결과에서 함수명/변수명 목록을 보고, 내가 의도한 함수들이 잘 export되었는지 확인할 수 있습니다. 만약 리스트에 없다면, 빌드 과정에서 내보내기 선언을 빼먹은 것입니다. C++ 함수가 목록에 있을 경우 맹글링된 이름 (예: ?FuncName@@YAXH@Z)으로 표시될 수 있는데, 이는 Visual C++의 장식 규칙에 따른 것입니다. 이런 경우 DLL을 배포할 때 C 인터페이스로 extern "C"를 사용하거나 .def 파일을 통해 이름을 지정하는 방식으로 이름 장식 문제를 해결해야 합니다.

참고: Visual Studio가 없다면, GNU Binutils의 objdump로도 Windows DLL의 export를 확인할 수 있습니다. 예를 들어 Linux 환경에서 objdump -p MyLibrary.dll를 실행하면 PE 포맷의 내보낸 심볼 (.edata 섹션) 정보를 읽을 수 있습니다. 출력에서 [Ordinal/Name Pointer] Table 아래에 함수 목록이 표시되며, Ordinal과 Name 열을 통해 export 심볼을 파악할 수 있습니다. 하지만 이 방법은 출력 해석이 다소 불편하므로, 일반적으로 Windows에서는 dumpbin이나 전용 GUI 툴을 사용하는 편이 낫습니다.

Dependency Walker 사용 (GUI)

Dependency Walker(depends.exe)는 Windows DLL의 의존성 내보낸/불러오는 심볼을 그래픽 인터페이스로 보여주는 오래된 도구입니다. 마이크로소프트 공식 툴은 아니지만 개발자 사이에 널리 사용됩니다. 사용 방법은 다음과 같습니다.

  1. Dependency Walker 프로그램(depends.exe)을 실행합니다. (공식 웹사이트에서 다운로드한 후 별도 설치 없이 실행 가능)
  2. 메뉴에서 File > Open 을 클릭하고 검사하고자 하는 DLL 파일(MyLibrary.dll)을 엽니다.
  3. 잠시 분석이 진행된 후, 화면에 해당 DLL의 내보낸 함수 목록이 표시됩니다. 보통 상단 창에 “Exports”로 불리는 목록이 나타나고, 하단 창에는 해당 DLL이 의존하는 다른 DLL들(Imports)이 나열됩니다.
  4. 상단 Exports 창에서 함수 이름, ordinal, 그리고 Hint 등을 확인할 수 있습니다. Dependency Walker는 dumpbin과 유사한 정보를 보여주지만, GUI로 제공하므로 스크롤과 정렬이 가능합니다.

예를 들어 MyLibrary.dll을 열었을 때 Dependency Walker의 Export 창에 MyFunction, MyVariable 등이 나타나면, 해당 심볼들이 외부로 export되었음을 쉽게 확인할 수 있습니다. 만약 의도한 함수가 리스트에 없으면, 앞서 언급한 대로 빌드 설정을 확인해야 합니다.

추가 도구: 이 외에도 DLL Export Viewer(NirSoft 제공)와 같은 GUI 툴도 있습니다. Dependency Walker와 비슷하게 DLL을 열면 export 심볼을 나열해주며, 좀 더 현대적인 Windows에서도 동작합니다. 그러나 기본 원리는 동일하므로 여기서는 자세한 설명을 생략합니다.

Windows에서의 빌드 옵션과 export 설정

Windows에서는 DLL을 생성할 때 어떤 심볼을 export할지 명시적으로 지정해야 합니다. 기본적으로 __declspec(dllexport) 키워드를 사용하여 함수나 변수를 선언하면, 컴파일 시 해당 심볼이 DLL의 export 테이블에 추가됩니다. 반대로 DLL을 사용하는 쪽에서는 __declspec(dllimport)를 통해 import 라이브러리 사용을 명시하지요. 간단한 예를 들면:

// dllheader.h (DLL의 공개 헤더)
__declspec(dllexport) int myFunction(int x);
// dll.c (DLL 구현)
__declspec(dllexport) int myFunction(int x) { return x * 2; }
static int internalUtil(int y) { return y * 3; }

위 코드에서 myFunction은 __declspec(dllexport)로 선언되었으므로 DLL의 내보낸 심볼 테이블에 포함됩니다. 반면 internalUtil은 static이며 dllexport가 없으므로 외부에 노출되지 않습니다. 이 DLL을 빌드한 뒤 dumpbin /exports나 Dependency Walker로 확인해보면 myFunction만 export 목록에 나타나고 internalUtil은 보이지 않게 됩니다.

만약 __declspec(dllexport)를 쓰지 않고 DLL을 빌드하면, 기본적으로 export되는 심볼이 없습니다. (예외적으로 DllMain처럼 시스템이 암묵적으로 참조하는 엔트리 포인트나, C++의 extern "C"로 내보낸 객체를 .def 파일에 기재한 경우 등이 있지만, 일반적인 C 코드에서는 지정하지 않는 한 export되지 않습니다.) 따라서 Windows DLL에서 API를 제공하려면 반드시 헤더에 export할 함수/변수를 __declspec(dllexport)로 선언하거나, 모듈 정의 파일(.def)의 EXPORTS 절에 해당 이름을 나열해야 합니다. .def 파일을 사용하는 방법은 대규모 프로젝트에서 유용하며, 이를 사용하면 소스코드에 컴파일 지시문을 넣지 않고도 export 심볼을 관리할 수 있습니다. .def 파일이 사용된 경우에도 dumpbin /exports 출력에 해당 함수들이 나타납니다 (이 때 함수 이름 옆에 ordinal만 지정되고 hint는 0으로 표시되는 등 출력 형태만 약간 다릅니다).

마지막으로, C++ 함수를 DLL에서 export할 때는 name mangling에 주의해야 합니다. C++ 함수명을 그대로 export하면 dumpbin이나 Dependency Walker에 맹글된 이름이 보이고, 호출 시에도 그 맹글된 이름을 알아야 합니다. 이를 피하려면 함수 선언을 extern "C"로 감싸서 C링케이지로 만들거나, .def 파일에서 Alias를 지정하여 이름을 변경할 수 있습니다. 일반적으로 DLL의 공개 API는 C 인터페이스로 구성하거나, C++ 클래스라도 공용 API에는 C 방식의 래퍼를 제공하기도 합니다.

 

728x90
728x90

블록 암호의 주요 패딩 알고리즘

패딩 알고리즘 개요

블록 암호는 정해진 크기(예: 64비트=8바이트, 128비트=16바이트 등)의 블록 단위로 데이터를 암호화합니다. 따라서 메시지 길이가 블록 크기의 배수가 아닌 경우 패딩(padding)을 추가하여 마지막 블록을 채워야 합니다. 패딩은 평문에 일정한 규칙으로 의미 없는 바이트들을 추가하는 작업으로, 복호화 시에도 동일한 규칙으로 이를 제거하여 원래의 평문을 복원할 수 있게 해줍니다. 패딩 방식에 따라 암호화된 결과의 길이가 블록 크기까지 늘어나며, 복호화 프로그램이 패딩된 바이트들을 정확히 인식하고 제거할 수 있도록 특정 패턴을 따릅니다.

패딩을 적용하지 않으면 마지막 블록이 부족한 상태로 암호화를 수행할 수 없을 뿐 아니라, 복호화 시에도 어디까지가 실제 평문이고 어디부터가 쓰레기 데이터인지 구분할 수 없습니다. 예를 들어 평문의 길이가 블록 크기의 정확한 배수인 경우, 별도의 조치 없이 암호화하면 복호화 시 마지막 바이트가 평문의 일부인지 패딩인지 구분할 수 없습니다. 이를 해결하기 위해 패딩 규격들은 항상 최소 1바이트 이상의 패딩을 추가하도록 정의되어 있으며, 평문 길이가 딱 맞아 떨어지는 경우에도 하나의 전체 블록을 패딩으로 채워 구분이 가능하게 합니다.

대표적인 패딩 알고리즘 목록

블록 암호에서 널리 사용되거나 표준으로 정의된 대표적인 패딩 방식은 다음과 같습니다:

  • PKCS#7 패딩 – 패딩으로 추가되는 모든 바이트의 값이 추가된 전체 패딩 바이트의 개수를 나타내는 방식 (PKCS#5는 8바이트 블록 암호에 국한된 동일 방식)
    예를 들어 4바이트를 패딩으로 추가해야 하면 각 바이트에 값 0x04를 넣습니다.
  • ANSI X.923 패딩 – 마지막 바이트를 추가된 패딩 바이트의 개수로 설정하고, 그 밖의 패딩 바이트들은 모두 0x00 (또는 구현에 따라 임의의 값)으로 채우는 방식
    예를 들어 4바이트 패딩 시 마지막 바이트는 0x04, 이전의 3바이트는 0x00으로 채웁니다.
  • ISO/IEC 7816-4 패딩 – 패딩의 첫 바이트로 0x80 하나를 추가하고, 남는 패딩 바이트는 모두 0x00으로 채우는 방식
    만약 패딩이 한 바이트만 필요하면 0x80 한 바이트만 추가합니다.
  • 제로 패딩(Zero Padding) – 필요한 패딩 바이트를 전부 0x00 값으로 채우는 방식
    다른 메커니즘과 달리 패딩 자체에 대한 별도 표준이 아니지만, 실행 환경에서 평문 길이를 별도로 알고 있거나 평문의 마지막에 널(byte 0x00)이 올 수 없는 경우에 종종 사용됩니다
  • ISO 10126 패딩 – 현재는 폐지된 이전 표준으로, 패딩 길이 이외의 패딩 바이트들을 임의의 랜덤 값들로 채우고 마지막 바이트에 패딩 개수를 기록하는 방식입니다

이외에도 특수한 상황에서는 비트 패딩(bit padding)이 사용되기도 하는데, ISO/IEC 7816-4 방식이 이에 해당하며 비트 단위 프로토콜에서도 적용할 수 있는 패딩 방식입니다.

패딩 알고리즘 동작 원리와 예시

PKCS#7 패딩 (PKCS#5 포함)

PKCS#7은 현재 가장 널리 쓰이는 패딩 규격으로, 추가되는 각 바이트의 값으로 추가된 패딩 바이트의 개수를 기록합니다. 즉, N바이트를 패딩해야 한다면 패딩 공간을 N으로 채운 값 N을 가진 바이트들로 모두 채우는 방식입니다. 예를 들어 블록 크기가 8바이트이고 마지막 블록에 5바이트의 패딩이 필요하다면, 0x05 값을 가진 바이트 5개를 추가합니다. 만약 평문 길이가 블록의 배수라 하더라도 규격상 항상 8바이트 패딩 블록을 추가해야 하므로, 이 경우에는 8바이트 모두를 값 0x08로 채운 하나의 패딩 블록이 추가됩니다 

PKCS#5 패딩은 PKCS#7의 특별한 경우로, 64비트(8바이트) 블록 암호(예: DES)에 대해서만 정의된 것입니다. 내용적으로 PKCS#7과 동일하며, 단지 블록 크기가 8바이트인 경우에 한해 쓰인 명칭일 뿐이므로 실제 구현이나 사용에서는 PKCS#7과 interchangeably 사용됩니다 

  • 예시: 평문이 "HELLO" (5바이트)이고 블록 크기가 8바이트라고 가정하면, PKCS#7 패딩을 적용하여 3바이트를 추가해야 합니다. 추가되는 바이트들은 모두 패딩 전체 크기인 0x03으로 채워지므로, 실제 암호화되는 최종 바이트열은 "HELLO\x03\x03\x03"이 됩니다. 복호화 측에서는 암호문을 복호화한 결과의 마지막 바이트를 확인하여 0x03이므로 마지막 3바이트를 제거함으로써 원래 평문을 복원합니다.

ANSI X.923 패딩

ANSI X.923 패딩은 미국 ANSI에서 정의한 데이터 패딩 방식으로, 패딩의 마지막 바이트에만 패딩 바이트의 개수를 기록하고 그 이전의 모든 패딩 바이트들은 0값으로 채우는 것이 특징입니다 패딩으로 최소 1바이트에서 최대 블록 크기 바이트까지 추가되며(블록 크기가 8일 경우 1~8바이트), 마지막에 추가된 패딩 바이트의 값은 추가된 패딩 바이트의 수 N을 의미하는 0xN입니다. 표준상 마지막 바이트 이외의 패딩 바이트 값은 무작위 값으로 채울 수 있으나, 일반 구현에서는 편의상 모두 0x00으로 채우는 경우가 많습니다

  • 예시: 블록 크기가 8바이트인데 마지막 블록에 4바이트의 패딩이 필요하다고 가정하면, ANSI X.923 방식에서는 추가되는 4바이트 중 마지막 바이트를 0x04로 설정하고 앞선 3바이트는 0x00으로 채웁니다 따라서 패딩된 최종 블록은 ... | DD DD DD DD 00 00 00 04 |의 형태를 갖습니다. 복호화 시 마지막 바이트의 값 0x04를 읽어 4바이트의 패딩이 추가되었음을 알고, ciphertext 끝의 4바이트를 제거하여 원문을 얻습니다.

PKCS#7 패딩과 ANSI X.923 패딩은 동일한 정보를 약속된 다른 형태로 기록할 뿐 기능적으로 등가(equivalent)합니다 두 방식 모두 마지막 바이트에 패딩 길이를 담고 있으며 최대 255 바이트까지 패딩을 표현할 수 있습니다. 차이는 PKCS#7은 패딩 바이트들을 모두 동일한 값으로 채우는 반면 ANSI X.923은 마지막 바이트를 제외한 패딩 영역을 0 또는 임의의 값들로 채운다는 점뿐입니다. 이 미묘한 차이로 인한 유의미한 이점은 거의 없으며, 실제 보안성이나 성능 면에서 두 방식은 대등합니다 

ISO/IEC 7816-4 패딩

ISO/IEC 7816-4 패딩은 국제 표준으로, 스마트카드 등의 파일 시스템 규격에서 유래한 패딩 방식입니다 패딩의 첫 번째 바이트를 0x80 값으로 설정하고 나머지 필요한 패딩 바이트들은 모두 0x00으로 채워 블록을 마무리하는 것이 특징입니다 이 방식은 종종 “80h 패딩”, “1-그리고-0 패딩(one-and-zero padding)” 또는 비트 패딩(bit padding)이라고도 불리는데, 이는 이 규격이 실제로 평문 데이터의 끝에 1 비트(1000 0000의 0x80)만 세우고 나머지 패딩 구간은 0으로 채우는 개념이기 때문입니다. 이러한 정의 덕분에 바이트 단위뿐 아니라 비트 단위로도 응용할 수 있어, 메시지 길이가 바이트 경계가 아닐 때도 동일 원리로 패딩을 적용할 수 있습니다.

  • 예시: 블록 크기가 8바이트이고 4바이트의 패딩이 필요한 경우, ISO 7816-4 방식으로 패딩하면 첫 패딩 바이트는 0x80, 나머지 3바이트는 0x00으로 채워집니다. 예를 들면 최종 블록이 ... | DD DD DD DD 80 00 00 00 | 형태가 됩니다. 만약 1바이트의 패딩만 필요했다면 0x80 한 바이트만 추가되어 마지막 블록은 ... | ... DD 80 |와 같이 0x80으로 끝나게 됩니다. 복호화 시에는 복호화된 데이터의 끝부분부터 역방향으로 스캔하며 최초로 나타나는 0x80 바이트를 찾고, 그 바이트부터 끝까지를 패딩으로 간주하여 제거합니다. 만약 역으로 탐색했을 때 0x80이 나타나지 않거나 규칙에 맞지 않는 값이 나오면 패딩 오류로 간주합니다.

ISO 7816-4 패딩은 명시적인 패딩 길이 숫자가 없기 때문에 이론적으로는 무제한 길이의 패딩도 지원할 수 있습니다 다만 일반적으로는 필요한 최소한의 바이트만 패딩으로 추가하며, 다른 패딩들과 마찬가지로 평문 길이가 블록 경계에 정확히 맞아떨어지더라도 0x80과 0으로 이루어진 한 블록의 패딩을 추가하도록 정의합니다 (그렇지 않으면 평문의 마지막 바이트가 우연히 0x80일 때 혼동이 발생할 수 있기 때문입니다).

제로 패딩 (Zero Padding)

제로 패딩은 말 그대로 모든 패딩 바이트를 0x00 값으로 채우는 방식입니다. 예를 들어 4바이트의 패딩이 필요하면 00 00 00 00을 추가하는 식입니다. 이 방식은 엄밀히 말해 암호화 표준으로 공식화된 적은 없지만, 구현이 단순하여 일부 상황에서 관습적으로 사용됩니다. 특히 암호화하는 데이터의 실제 길이를 별도의 메타데이터로 알고 있는 경우나, 평문 데이터가 문자열이고 문자열의 끝에는 0x00 (널 문자) 등이 올 수 없는 환경에서 활용됩니다. 이러한 경우에는 복호화 후 미리 알고 있던 길이만큼만 취하거나, 또는 문자열의 경우 '\0' 이후를 무시하는 방식으로 패딩을 제거하여 원문을 복원할 수 있습니다.

  • 예시: 평문 "ABC" (3바이트)를 8바이트 블록 암호로 암호화한다고 가정하면, 남는 5바이트를 모두 0x00으로 패딩할 수 있습니다. 암호화되는 최종 바이트열은 "ABC\x00\x00\x00\x00\x00"가 되며, 복호화한 쪽에서 원본 데이터의 길이(3바이트)를 알고 있다면 앞의 3바이트 "ABC"만을 취하고 이후의 0x00 바이트들은 버립니다.

제로 패딩은 구현이 가장 간단하지만 패딩을 식별할 수 있는 정보가 존재하지 않으므로 안전한 복원에는 별도의 전제 조건이 필요합니다. 예를 들어 평문 데이터가 임의의 바이너리 데이터일 경우 마지막에 0x00 바이트가 포함되어도 전혀 이상하지 않으므로, 패딩으로 추가된 0x00들과 평문의 실제 0x00 데이터를 구분할 방법이 없습니다. 따라서 평문 길이를 별도로 전달하거나, 혹은 평문이 특정 문자 인코딩 문자열이라서 0x00은 종단 문자로서만 등장한다는 보장이 있을 때에만 사용이 권장됩니다. 만약 이러한 조건 없이 제로 패딩을 사용할 경우 복호화 결과에서 패딩과 실제 데이터를 혼동하여 데이터 무결성이 깨질 위험이 있습니다.

패딩 알고리즘 비교 및 활용

대표적인 패딩 방식들의 원리를 살펴보았으므로, 이제 각 방식별로 어떤 상황에 적합하고 어떤 장단점이 있는지 비교해보겠습니다. 아래는 각 패딩 방식의 권장 활용 분야와 장단점을 정리한 내용입니다:

  • PKCS#7 패딩
    • 적합한 상황: 범용적으로 가장 많이 사용되는 패딩으로, 특별한 요구 사항이 없다면 기본 선택지로 적합합니다. 다양한 프로그래밍 언어나 암호화 라이브러리에서 기본 제공되며, 과거 TLS 등 여러 프로토콜에서도 채택되었을 만큼 폭넓게 활용됩니다
    • 장점: 표준으로 명확히 정의되어 있어 상호운용성이 높고 구현이 쉬우며, 패딩 길이 정보가 명시적으로 포함되어 있어 복호화 시 처리도 간단합니다. 또한 모든 패딩 바이트가 동일한 값이므로, 패딩 무결성 검증시 한 번에 검사가 가능하여 오류를 쉽게 잡아낼 수 있습니다.
    • 단점: 평문이 블록 경계에 딱 맞을 경우에도 전체 블록을 추가로 패딩해야 하므로 데이터 길이가 늘어나는 오버헤드가 있습니다. 예컨대 16바이트 단위 AES에서 16바이트짜리 평문 하나를 암호화하면 16바이트를 더 패딩으로 붙여 총 32바이트 암호문이 됩니다. 하지만 이러한 단점은 ANSI X.923 등의 다른 표준 패딩도 동일하며, 패딩 자체로 인한 성능 부담은 일반적으로 미미한 편입니다.
  • ANSI X.923 패딩
    • 적합한 상황: 과거 금융권 등 특정 표준을 준수해야 하는 시스템에서 주로 사용되었습니다. 현재는 PKCS#7로 대체되는 추세지만, 기존 VB6/ASP.NET 등 일부 구현이 ANSI X.923을 기본 사용하는 경우가 있어 이들과의 호환이 필요할 때 선택합니다.
    • 장점: PKCS#7과 마찬가지로 명시적인 패딩 길이 정보를 포함하여 확실한 패딩 제거가 가능합니다. 모든 패딩 바이트를 0으로 채우므로 필요시 평문을 패딩 전후로 시각적으로 구분하기가 다소 수월할 수 있으며 (패딩 영역이 모두 00으로 보임), 임의의 값으로 패딩을 채울 유연성도 있습니다. 또한 PKCS#7로 패딩된 암호문을 X.923 패딩으로 간주하고 해제하거나 그 반대의 처리도 이론적으로는 가능할 정도로 유사합니다.
    • 단점: PKCS#7 대비 특별한 우위점이 거의 없어 현대의 암호 표준에서 잘 쓰이지 않는 방식입니다. 패딩 바이트 중 마지막 한 바이트만 유의미하고 나머지는 모두 0이어서, 만약 패딩 영역에서 단일 바이트 오류가 발생해도 (예: 0이 아닌 값으로 변조돼도) 패딩 길이만 맞으면 검출이 어려울 수 있다는 지적이 있습니다. 실제 구현에서는 패딩 바이트 전체에 대한 무결성 검사를 수행하므로 이러한 차이는 크지 않지만, 패딩 오라클 공격 등 취약성 관점에서 PKCS#7에 비해 더 안전하다고 볼 근거는 없습니다.
  • ISO/IEC 7816-4 패딩
    • 적합한 상황: 스마트카드 응용이나 특정 임베디드 시스템에서 주로 사용됩니다. 또한 비트 단위의 메시지 길이를 처리해야 할 때 (예를 들어, 마지막에 1 비트만 세우고 남은 비트를 패딩하는 경우) 이 방식을 일반화하여 쓸 수 있습니다. 일반 소프트웨어 환경에서는 드물지만, 암호 라이브러리에서 옵션으로 제공되는 경우가 있어 특수한 프로토콜 구현에 사용되기도 합니다.
    • 장점: 패딩에 고정된 구분 마커(0x80)를 사용하므로 패딩의 시작 지점을 뚜렷이 표시할 수 있으며, 패딩 길이와 무관하게 일정한 패턴을 가집니다. 0x80 한 바이트만 찾으면 패딩 전체를 식별할 수 있기 때문에, 블록 경계가 아닌 임의의 길이(비트 단위)에도 확장할 수 있는 융통성이 있습니다 . 또한 패딩 영역이 모두 0x80 또는 0으로 구성되어 있어, 패딩 데이터가 암호문에 미치는 영향이 상대적으로 단순합니다.
    • 단점: 패딩을 제거하려면 역순으로 바이트를 검사해야 하며 (특히 여러 블록에 걸쳐 패딩이 존재할 경우 조금 더 복잡해짐) 최악의 경우 평문의 처음까지 확인해야 하는 오버헤드가 있습니다. 하지만 블록 크기가 크지 않으므로 이 성능 영향은 미소합니다. 또한 다른 표준에 비해 대중적인 구현체가 적어 호환성 측면에서 덜 보편적입니다. 패딩의 특정 바이트 값(0x80) 의존 특성상, 평문에 0x80이 많이 등장하는 경우 패딩 부분과 혼동될 여지가 있지 않냐는 의문이 있을 수 있으나, 규칙상 평문 끝에 바로 패딩 마커를 붙이도록 정의되어 있어 실제 혼동이 발생하지는 않습니다.
  • 제로 패딩
    • 적합한 상황: 평문의 실제 길이를 별도로 관리하는 프로토콜이나, 평문이 문자열 데이터로 마지막에 0x00 문자가 올 수 없다고 보장되는 경우에 한해 사용이 권장됩니다. 예를 들어 네트워크 프로토콜에서 암호화 전에 평문의 길이를 따로 전송하거나, 암호화 파일 포맷에서 원본 데이터 크기를 별도 메타데이터로 저장하는 경우 등이 이에 해당합니다. 이 밖에 C언어 스타일의 널 종단 문자열(null-terminated string)을 암호화할 때 문자열 끝의 '\0'들을 패딩으로 활용하는 용도로 쓰이기도 합니다 
    • 장점: 가장 구현이 단순하며, 추가적인 정보 없이도 직관적으로 패딩을 채울 수 있기 때문에 오류 가능성이 적은 편입니다. 또한 패딩 바이트가 모두 0x00이므로 사람이 확인하기에도 분명하고, 특정 상황에서는 패딩 제거 연산을 생략하고도 자연스럽게 평문을 얻을 수 있습니다 (예: null-terminated 문자열을 복호화한 경우, 문자열 연산에서 '\0' 이후를 읽지 않으므로 패딩을 자동 무시하게 됨).
    • 단점: 패딩과 평문을 구분할 명확한 방법이 없기 때문에 위험합니다. 별도의 평문 길이 정보가 없으면, 복호화 결과의 뒤쪽에 있는 0x00들이 원래 평문에 포함된 값인지 패딩인지 판단할 수 없습니다. 이러한 모호성 때문에 일반 암호 프로토콜에는 잘 사용되지 않으며, 표준화된 규격도 존재하지 않습니다. 잘못 사용할 경우 데이터 일부가 손실되거나 불필요한 바이트가 덧붙는 부작용이 발생할 수 있습니다.
  • ISO 10126 패딩 (참고)
    • 적합한 상황: 현재는 권고되지 않는 구식 방식으로, 특별한 이유가 없다면 사용되지 않습니다. 과거에 임의 바이트로 채워서 분석을 어렵게 하려는 의도로 고안되었으나, 현대에는 임의의 IV나 AEAD 알고리즘으로 동일한 효과를 얻기 때문에 의미가 줄어들었습니다.
    • 장점: 패딩 바이트들을 랜덤한 값들로 채우므로 암호문만 보았을 때 패딩 부분에 규칙성이 거의 없습니다. 이론적으로 동일한 평문이라도 암호화를 할 때마다 패딩이 달라질 수 있어, 마지막 블록의 암호문이 매번 바뀌는 효과를 냅니다.
    • 단점: ANSI X.923과 마찬가지로 마지막 한 바이트 외에는 패딩 데이터로서 별 의미가 없고, 그 값들이 랜덤이므로 복호화 시 검증이 사실상 불가능합니다. 즉 패딩 길이만 맞으면 어떤 값이든 받아들이게 되므로, 오류 탐지가 어렵고 보안적으로 이점이 없습니다. ISO 10126 표준 자체도 2007년에 폐지되었으며 , 현재 주요 라이브러리에서는 지원하지 않는 경우가 많습니다.

보안적 고려사항 (패딩 오라클 공격)

패딩 적용 시에는 **패딩 오라클 공격(padding oracle attack)**에 대한 대비가 필요합니다. 패딩 오라클 공격이란, 암호화된 데이터의 패딩 유효성 여부가 외부에 누설되는 취약점을 이용하여 공격자가 암호문의 일부를 평문으로 해독하거나 임의의 평문을 암호화해내는 기법입니다. 예를 들어 서버 측에서 암호문을 복호화한 뒤 패딩이 올바르지 않으면 에러 메시지를 보내주거나 다른 응답을 보인다면, 공격자는 이를 이용해 평문의 바이트를 하나씩 역추론하거나 잘못된 패딩을 이용해 암호문을 조작할 수 있습니다. 2002년 Serge Vaudenay의 논문으로 CBC 모드에서의 패딩 오라클 공격이 처음 널리 알려졌으며, 이후 웹 애플리케이션 등 다양한 환경에서 실습적으로 이러한 공격이 구현되었습니다.

패딩 오라클 공격은 패딩 방식 자체의 문제라기보다는 구현상의 정보 노출 문제입니다. PKCS#7, ANSI X.923, ISO 7816-4 등 어떤 패딩을 쓰든지, 복호화 과정에서 “패딩이 유효한지” 여부를 공격자가 알아챌 수 있다면 모두 공격에 노출될 수 있습니다. 패딩의 구조적인 차이가 공격 난이도에 약간 영향을 줄 수는 있지만 근본적인 해결책이 되지는 않습니다. 따라서 다음과 같은 대응책을 적용하는 것이 중요합니다:

  • 패딩 오류 정보를 숨기기: 복호화 후 패딩 오류가 발생하더라도 공격자에게 동일한 형태의 응답을 돌려주고, 패딩 오류와 다른 오류를 구별할 수 없게 만듭니다. 예를 들어 패딩이 잘못되어도 그냥 일반적인 “해독 실패” 오류를 내고 추가 정보를 주지 않거나, 오류가 발생한 경우 시간을 일부러 지연시키는 등의 방법으로 공격자가 판단하기 어렵게 합니다.
  • 무결성 검증 (MAC 또는 인증): **메시지 인증 코드(MAC)**나 전자서명 등을 활용하여 평문 패딩까지 포함한 무결성을 검증한 후에만 평문을 처리합니다. 일반적으로는 암호문에 대한 HMAC을 함께 전송하여 복호화 전에 검증하거나, 최신 프로토콜에서는 인증된 암호화(AEAD) 모드를 사용하여 암복호화 시 자동으로 무결성을 확인합니다. 이를 통해 패딩이 잘못된 경우에도 애초에 MAC 검증에서 걸러지므로 패딩 오류 여부가 외부에 드러나지 않게 됩니다.
  • 패딩 대신 스트림 모드 사용 고려: 기술적으로는 패딩을 사용하지 않도록 암호 운용 방식을 선택하는 것도 방법입니다. 예를 들어 CTR이나 CFB와 같은 스트림 모드로 블록 암호를 운영하면 별도의 패딩 없이도 임의 길이의 데이터를 처리할 수 있습니다. 다만 이러한 변경은 시스템 전반의 구조를 바꾸는 것이므로 기존 프로토콜을 유지하면서는 어렵고, 가능하다면 GCM과 같은 AEAD 모드로 교체하는 것이 근본적인 해결책이 될 수 있습니다.

요약하면, 패딩 알고리즘을 선택할 때에는 용도와 환경에 따라 적절한 방식을 고르고, 구현 시에는 패딩으로 인한 보안 취약점이 발생하지 않도록 주의해야 합니다. 대부분의 현대적인 시스템에서는 PKCS#7 패딩을 기본으로 사용하며, 동시에 패딩 오라클 공격을 막기 위한 무결성 체크나 인증된 암호화를 적용하는 것이 일반적입니다. 패딩 자체는 블록 암호를 유연하게 사용하기 위한 필수 요소이므로, 그 원리와 각 방식의 특성을 이해하고 안전하게 활용하는 것이 중요합니다.

728x90
728x90

 

1. 패키지 저장소 업데이트 및 설치

apt-get update
apt-get install -y software-properties-common
add-apt-repository ppa:ubuntu-toolchain-r/test
apt-get update
apt-get install -y gcc-4.7 g++-4.7

 

 

2. GCC 4.7 기본값으로 설정

update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.7 50
update-alternatives --config gcc

 

 

 

 

728x90
728x90

 

Docker는 멀티 아키텍처를 지원하기 때문에  Apple Silicon에서 x86_64 아키텍처 도커이미지를 실행할 수 있다.

 

 

1. QEMU 설정 확인

docker run --rm --platform linux/amd64 busybox echo "QEMU Support Confirm"

 

 

 

2. x86_64 이미지 가져오기

 docker pull --platform linux/amd64 ubuntu:14.04

 

 

 

3. x86_64 아키텍처 컨테이너 실행

docker run --rm -it --platform linux/amd64 ubuntu:14.04 bash

 

728x90

+ Recent posts