diff --git a/.github/workflows/security-and-static-analysis.yml b/.github/workflows/security-and-static-analysis.yml
index d70a23a..cd91e21 100644
--- a/.github/workflows/security-and-static-analysis.yml
+++ b/.github/workflows/security-and-static-analysis.yml
@@ -2,9 +2,9 @@ name: Security & Static Analysis
on:
push:
- branches: [ "master", "develop" ]
+ branches: [ "master", "feature/*" ]
pull_request:
- branches: [ "master", "develop" ]
+ branches: [ "master", "feature/*" ]
schedule:
- cron: '30 1 * * 0' # 매주 일요일 01:30 정기검진
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 0ead08a..2547228 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -9,6 +9,10 @@
diff --git a/README.md b/README.md
index 7b6cfb1..823687f 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,9 @@

-얽힘 라이브러리(EntanglementLib)는 양자 내성 암호(Post-Quantum Cryptography, PQC) 기술을 기반으로 설계된 고성능 보안 및 유틸리티 라이브러리입니다. 본 라이브러리는 진화하는 디지털 위협 환경에 대응하여 최고 수준의 보안과 시스템 안정성을 제공하는 것을 목표로 합니다.
+얽힘 라이브러리(EntanglementLib)는 모든 보안 연산을 안전하며, 빠르게 처리하도록 설계된 군사급 보안 라이브러리입니다. 고전 및 양자-내성 암호화(Post-Quantum Cryptography, PQC) 기술을 제공하며, 이를 사용한 미래지향적 TLS 프로토콜을 제공합니다.
+
+진화하는 디지털 위협 환경에 대응하여 최고 수준의 보안과 시스템 안정성을 제공하는 것을 목표로 합니다.
> [English README](README_EN.md)
@@ -14,125 +16,55 @@
얽힘 라이브러리의 모든 설계는 '군사적 보안', '금융 및 대규모 엔터프라이즈 환경에서의 안전한 인프라 확립'을 위한 보안성(security)을 최우선 원칙으로 합니다. 얽힘 라이브러리는 잠재적 보안 취약점을 원천적으로 방지하고, 데이터 무결성을 보장하도록 구현되었습니다. 두 번째 핵심 가치는 안정성(stability) 으로, 예측 가능하고 일관된 성능을 보장하기 위해 Rust 네이티브 연산을 적용하여 매우 안전하게 메모리 효율성을 극대화하고, 체계적인 오류(또는 예외) 처리 메커니즘을 갖추었습니다.
-## 강점
-
-얽힘 라이브러리는 다음의 강점을 보유하고 있습니다.
-
-1. 잔류 데이터 방지 (Anti-Data Remanence) 및 메모리 소거
- - 자바의 메모리 관리 모델인 가비지 컬렉터(Garbage Collection, GC)가 보안에 취약할 수 있다는 점을 정확히 파악하고 이를 기술적으로 극복하기 위한 기술이 탑재되었습니다.
- - 이 기술을 위해 [`entlib-native` 네이티브 라이브러리](https://github.com/Quant-Off/entlib-native)를 사용하여 안전하게 소거하도록 설계했습니다.
- - 모든 민감 정보를 안전하게 관리하기 위해 [데이터 컨테이너 기능](https://github.com/Quant-Off/entanglementlib/blob/master/src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java)을 제공합니다.
-2. 최신 PQC 표준 준수
- - FIPS 203, 204, 205에 따른 ML-KEM, ML-DSA, SLH-DSA 등의 NIST 표준화가 완료된 최신 알고리즘 및 AES, ARIA, ChaCha20과 같은 고전 알고리즘을 네이티브에서 처리하도록 하여 안전한 연산이 가능합니다.
-3. 아키텍처 및 디자인 패턴
- - 팩토리 패턴 및 인터페이스를 명확히 분리했습니다. [암호화 알고리즘은 전략적](https://github.com/Quant-Off/entanglementlib/tree/master/src/main/java/space/qu4nt/entanglementlib/security/crypto)으로 호출하여 사용할 수 있습니다.
-4. 예외 처리
- - 표준 예외를 그대로 던지지 않도록 했습니다. 얽힘 라이브러리만의 커스텀 예외 클래스로 래핑하여 명확한 문맥(context)을 제공했습니다.
- - 보안 측면에서 문제를 발견한 경우, 시스템이 해결 방법을 제시하고, 우려되는 공격(해킹)을 방어하도록 설계했습니다.
-
-## 주요 기능
-
-얽힘 라이브러리는 강력한 암호화 기능과 개발 생산성 향상을 위한 유틸리티를 포함합니다.
-
-| 모듈 | 기술 명세 |
-|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `security.crypto` | 양자 내성 암호(PQC) 및 현대 암호화 알고리즘을 제공합니다.
- **PQC (NIST 표준)**: `ML-DSA` (Digital Signature Algorithm), `ML-KEM` (Key Encapsulation Mechanism), `SLH-DSA`를 지원하여 양자 컴퓨팅 시대에 대응하는 보안 체계를 구축합니다.
- **대칭키 암호**: `ChaCha20-Poly1305` AEAD(Authenticated Encryption with Associated Data)를 지원하여 높은 수준의 기밀성과 무결성을 동시에 제공합니다.
- **고성능 연산**: 주요 알고리즘은 SIMD(Single Instruction, Multiple Data) 명령어 셋을 활용하여 병렬 처리를 극대화하고 연산 속도를 최적화합니다.
- **전략 패턴**: `RegistrableStrategy` 및 `EntLibCryptoRegistry`를 통해 알고리즘을 유연하게 등록하고 사용할 수 있습니다. |
-| `security.communication` | 안전한 통신을 위한 세션 및 TLS 관련 기능을 제공합니다.
- **세션 관리**: `Session`, `Participant`, `SessionConfig` 등을 통해 안전한 세션을 생성하고 관리합니다.
- **TLS 지원**: `Server`, `ServerConfig` 등을 통해 TLS 기반의 보안 통신 채널을 구축할 수 있습니다. |
-| `entlibnative` | 네이티브 라이브러리와의 연동을 담당합니다.
- **데이터 보호**: `SensitiveDataContainer`를 통해 민감한 데이터를 안전하게 관리하고 소거합니다.
- **네이티브 연동**: `NativeLinkerManager`를 통해 Rust 등으로 작성된 고성능 네이티브 라이브러리를 로드하고 사용합니다. |
-| `util` | 보안 및 개발 편의성을 고려한 고성능 유틸리티를 제공합니다.
- **입출력 및 청크 처리**: `EntFile`, `ChunkProcessor` 등을 통해 대용량 파일 및 데이터를 효율적으로 처리합니다.
- **보안 유틸리티**: `Password`, `SecureCharBuffer` 등을 통해 비밀번호 및 민감한 문자열을 안전하게 다룹니다.
- **기타**: `Hex`, `Hash`, `Async` 등 다양한 유틸리티 클래스를 제공합니다. |
-| `exception` | 안정적인 오류 처리를 위한 체계적인 예외 계층 구조를 제공합니다.
- **보안 예외**: `EntLibSecureException` 및 하위 클래스들을 통해 보안 관련 오류를 명확하게 처리합니다.
- **암호화 예외**: `EntLibCryptoException` 등을 통해 암호화 과정에서 발생하는 오류를 세분화하여 관리합니다.
- **치명적 오류**: `EntLibError` 및 하위 클래스들을 통해 시스템의 치명적인 오류를 정의합니다. |
-
-## 기술 명세
-
-얽힘 라이브러리가 네이티브 라이브러리와 어떻게 상호 작용하는지, 네이티브에서 보안 연산은 어떻게 수행하는지에 대한 기술 명세를 [이 곳](TECHNICAL.md)에 작성됩니다.
-
-## 벤치마킹 기록
-
-얽힘 라이브러리에서 FFM API를 사용하여 Rust 네이티브 함수를 호출할 떄 발생하는 나노세컨드 지연 등 다양한 연산에 대한 브릿지 벤치마킹을 수행하고 있습니다. 이러한 작업은 성능 및 보안성에 직결되며, 최적의 코드를 창출하기 위해 중요한 역할을 합니다.
-
-얽힘 라이브러리의 벤치마킹은 [`native-benchmark`](native-benchmark) Rust 프로젝트에서 진행되며, 벤치마킹 기록은 [이 곳](NATIVE_BENCHMARK.md)에 작성됩니다.
-
-## 시작하기
+## 기술
-### 1. 요구 사항
+얽힘 라이브러리는 모든 보안 연산을 [Rust 기반 네이티브](https://github.com/Quant-Off/entlib-native)를 통해 수행합니다. 네이티브에선 `heap` 메모리 할당에 따른 가비지 컬렉터(Garbage Collection, GC)의 청소 메커니즘에서 발발될 수 있는 모든 보안 약점을 파훼합니다. Java 측의 민감 데이터를 `off-heap`으로 받아 작업을 수행하고, 호출자 또는 피호출자 패턴을 통해 해당 포인터의 데이터를 즉각적으로 안전하게 소거합니다.
-얽힘 라이브러리는 다음의 환경에서 개발되었습니다. 다른 버전에 대한 세부적인 테스트는 진행되지 않은 상태입니다.
+Java 측에서 네이티브와 상호 작용할 때, 단순히 JNI(Java Native Interface) 기능이 사용되지 않습니다. 핵심적인 기술은 [JEP 389](https://openjdk.org/jeps/389), [JEP 454](https://openjdk.org/jeps/454) 개선안에 의거한 고급적 네이티브 호출 기능인 Linker, FFM API(Foreign Function & Memory API)이며, 네이티브 측에선 캡슐화된 로직을 통해 FFI(Foreign Function Interface)로 연동됩니다.
-- Java 25
-- Gradle 9.2.0
+> [!TIP]
+> 네이티브에 대한 배경 및 개요가 궁금하시다면 [이 곳](https://qu4nt.space/projects/entlib-native)을 참고하세요.
+>
+> 또는 얽힘 라이브러리의 배경에 대해 궁금하시다면 [이 곳](https://qu4nt.space/projects/entanglementlib)을 참고하세요.
-얽힘 라이브러리는 NIST FIPS 203, 204, 205 표준화에 포함된 알고리즘을 안정적으로 사용하기 위해 Java 25 이상의 버전을 사용합니다.
+이 라이브러리 내에서 사용자의 데이터는 바이트 배열(`byte[]`)이나 문자 배열(`char[]`)로 관리되지 않습니다. 이러한 타입은 `heap` 메모리, 나아가 GC에게 주도권을 주는 셈입니다. 이러한 원시적 사용 대신 `SensitiveDataContainer` 객체를 사용할 수 있습니다. 이 객체는 민감 데이터의 소유권을 넘겨받고 네이티브에 안전히 넘겨 작업 처리를 안전하고 효율적으로 할 수 있도록 도와줍니다. 좀 더 구체적으로, 해당 객체는 [Rust의 RAII(Resource Acquisition Is Initialization) 패턴](https://doc.rust-lang.org/rust-by-example/scope/raii.html)과 유사하게 인스턴스화 시점에 자원을 획득하고 `close()` 호출 시점에 자원을 해제합니다. 이러한 개념은 Java에서 꽤 특이할 수도 있습니다.
-### 2. 환경 변수 설정
+## 멀티모듈
-`EntanglementLib`의 정상적인 동작을 위해 다음 환경 변수 설정이 필요합니다.
+얽힘 라이브러리는 이제 멀티모듈 프로젝트입니다. 각 모듈의 역할은 작업 및 실용적 어노테이션, 보안 그리고 각종 편의성 도구를 포함한 유틸리티로 나눠집니다. 어노테이션 및 코어 모듈은 보안 모듈에서 핵심적으로 사용되지만, 보안 모듈은 다른 모듈에서 절대로 사용되지 않습니다.
-| 변수명 | 설명 | 기본값 | 필수 여부 |
-|-------------------------|------------------------------------------------|-----|-------|
-| `ENTLIB_NATIVE_BIN` | 네이티브 라이브러리의 바이너리가 포함된 디렉터리 경로를 지정합니다. | - | 필수 |
-| `ENTANGLEMENT_HOME_DIR` | 얽힘 라이브러리 내부에서 사용되는 리소스가 저장될 디렉터리 경로를 지정합니다. | - | 필수 |
+| 모듈 | 기능 |
+|---------------|------------------------------------------------------------------|
+| `security` | 핵심 보안 모듈입니다. 네이티브와의 상호 작용을 위한 로직과, FFI를 통해 연동된 갖가지 보안 기능을 제공합니다. |
+| `core` | 예외, 국제화 및 비동기, 청크 작업, 문자열, 자료구조를 관리하는 유틸리티 기능을 제공합니다. |
+| `annotations` | 간편한 코드 설계 및 사용자의 코드 이해 복잡도를 개선하기 위한 어노테이션이 포함되어 있습니다. |
-```bash
-# 예시: Linux/macOS
-export ENTLIB_NATIVE_BIN="/path/to/entlib-native/release"
-export ENTANGLEMENT_HOME_DIR="/path/to/entanglementlib"
-```
-```bash
-# 예시: Windows
-setx ENTLIB_NATIVE_BIN "C:\path\to\entlib-native\release"
-setx ENTANGLEMENT_HOME_DIR "C:\path\to\entanglementlib"
-```
-
-### 3. 저장소 클론 또는 Maven 저장소 사용
-
-다음의 명령을 통해 저장소를 클론할 수 있습니다.
-
-```shell
-$ git clone https://github.com/Quant-Off/entanglementlib.git
-$ cd entanglementlib
-```
+## 기술 명세
-의존성으로 등록하려는 경우 간편히 `Maven` 저장소를 이용할 수도 있습니다.
+얽힘 라이브러리에 대한 상세한 기술 명세를 작성 중에 있습니다.
-#### Maven Project
+## 벤치마킹 기록
-```xml
-
-
- space.qu4nt.entanglementlib
- entanglementlib
- 1.0.0
-
-
-```
+얽힘 라이브러리에서 FFM API를 사용하여 Rust 네이티브 함수를 호출할 떄 발생하는 나노세컨드 지연 등 다양한 연산에 대한 브릿지 벤치마킹을 수행하고 있습니다. 이러한 작업은 성능 및 보안성에 직결되며, 최적의 코드를 창출하기 위해 중요한 역할을 합니다.
-#### Gradle Project
+이 알파 버전에서 많은 벤치마킹 작업이 예정되어 있습니다. JMH(Java Microbenchmark Harness)를 통해 이 작업을 진행할 예정이며, 완료되는데로 새로운 문서에 정리하겠습니다.
-```kotlin
-repositories {
- mavenCentral()
-}
+## 기여
-dependencies {
- implementaion("space.qu4nt.entanglementlib:entanglementlib:1.0.0")
- // or Groovy
- // implementaion 'space.qu4nt.entanglementlib:entanglementlib:1.0.0'
-}
-```
+이 프로젝트는 현재 `Alpha` 버전이며, 아직 많이 부족합니다. 여러분의 피드백을 적극적으로 받을 준비가 이미 되어 있습니다. 얽힘 라이브러리는 단순히 PQC 알고리즘을 제공하는 것만이 아닌, 체계적으로 사용자 환경의 인프라 보안을 감시하고 사용자에게 해결책을 찾아주는 유능한 도구로써 사용되도록 개발됩니다. 최신 릴리즈부턴 이 신념에 강력한 초점을 맞출 것입니다.
## TODO
-이 프로젝트는 아직 많이 부족합니다. 얽힘 라이브러리는 미래에 금융 및 보안 인프라 프로덕션에서 사용할 수 있도록 다음의 TODO를 명확히 하고자 합니다.
+얽힘 라이브러리는 미래에 금융 및 보안 인프라 프로덕션에서 사용할 수 있도록 다음의 TODO를 명확히 하고자 합니다.
-- [ ] 공급자 유동화 및 팩토리 최적화
- - 사용자의 선택에 따라 Java의 기본 공급자를 사용할 수 있도록 수정해야 합니다. 그리고 `InternalFactory` 클래스를 포함한 대부분의 클래스에서 제공되는 팩토리를 최적화해야 합니다.
-- [ ] 전체 디자인 패턴 최적화
- - 현재 코드는 스파게티라고 해도 과언이 아닐 만큼 더러운 부분이 많이 보입니다. 제 방처럼 쾌적한 코드를 작성할 수 있도록 수정해야 합니다.
+- [ ] TLS 통신 로직 추가
+- [ ] 복합 검증 작업 준비 및 수행
+- [ ] 커스텀 예외 최적화
- [ ] JPMS 적용
- 안전한 캡슐화와 일관된 호출(또는 사용) 패턴이 완성되면 JPMS를 통해 캡슐화된 패키지를 모듈로서 관리하려고 합니다.
-- [ ] `BouncyCastle` 의존성 제거
- - 이제 `1.1.0` 릴리즈부턴 `BC` 의존성을 최소화하며, 끝내 제거하려고 합니다. 현재 AES, ARIA, ChaCha20 같은 고전 알고리즘은 여전히 `BC`에 의존하고 있습니다. 얽힘 라이브러리의 보안 철학을 확실히 하고자 모든 암호학적 연산은 `entlib-native`에서 수행하기로 결정했습니다.
+- [ ] 외부 의존성 최소화
+ - 이제 `1.1.0` 릴리즈부턴 `BouncyCastle` 의존성을 최소화하며, 끝내 제거하는 데 성공했습니다. 현재 코드 작성에 필요한 몇 가지 유용한 도구를 제공하는 의존성은 여전히 남아 있지만, 이들도 끝내 최소화될 예정입니다.
- [ ] `i18n` 업데이트
- 최신 릴리즈 개발을 수행하며 다국어 지원을 많이 누락했습니다. 구성 설정에 따라 각 언어별로 로깅을 지원할 수 있도록 수정해야 합니다.
@@ -151,18 +83,6 @@ dependencies {
- [X] 보안 기능에 대한 기술 명세 작성
- **해결**: 얽힘 라이브러리의 중요한 보안 관련 기능을 [별도의 문서](TECHNICAL.md)에 작성했습니다.
-## 기여
-
-초기 버전에서 얽힘 라이브러리는 'BouncyCastle 래퍼', '어중간한 데이터 소거', ... 와 같이 정말 애매모호한 목표를 가지고 있었습니다. 이 부분에 대해 꽤 많은 시간을 투자해 곰곰히 생각했으며, 얽힘 라이브러리만의 고유한 목표를 다시금 정립했습니다.
-
-이제 얽힘 라이브러리는 단순히 PQC 알고리즘을 제공하는 것만이 아닌, 체계적으로 사용자 환경의 인프라 보안을 감시하고 사용자에게 해결책을 찾아주는 유능한 도구로써 사용되도록 개발됩니다. 최신 릴리즈부턴 이 신념에 강력한 초점을 맞출 것입니다.
-
-그렇기 때문에... 얽힘 라이브러리의 궁극적 목표를 완성시키기 위해 여러 개발자분들의 힘이 필요합니다. 언제든 코드에 대한 피드백을 남겨주세요. 퀀트 팀에게 아주아주 큰 힘이 됩니다!
-
-## Alpha
-
-현재 얽힘 라이브러리는 뭉친 업데이트 내용을 재빨리 반영하기 위해 꽤 성급하게 `1.1.0-Alpha`를 공개한 상태입니다. 이 버전의 안정화 버전은 `1.1.0` 정식 릴리즈로 수정되서 반영될 예정입니다. 아무래도 1인 개발이다 보니 속도가 더뎌지는 문제가 있으나, 모든 수정까지 얼마 남지 않았습니다.
-
## 라이선스
본 프로젝트는 `PolyForm Noncommercial License 1.0.0`을 따릅니다. 이 프로젝트 내에서 `entlib-native`를 동시 관리하는 탓에 라이선스가 가끔 `MIT`로 잘못 반영될 때가 있지만, 여전히 `PolyForm` 라이선스를 따른다는 것을 참고해주세요. 이 라이선스에 관해 자세한 내용은 [LICENSE](LICENSE) 파일을 참고하세요.
diff --git a/README_EN.md b/README_EN.md
index 7ea589e..212c900 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -6,169 +6,89 @@

-EntanglementLib is a high-performance security and utility library designed based on Post-Quantum Cryptography (PQC) technology. This library aims to provide the highest level of security and system stability in response to the evolving digital threat landscape.
+EntanglementLib is a military-grade security library designed to process all security operations safely and quickly. It provides classical and Post-Quantum Cryptography (PQC) technologies, and offers a future-oriented TLS protocol using them.
+
+It aims to provide the highest level of security and system stability in response to the evolving digital threat environment.
> [Korean README](README.md)
## Core Philosophy
-All designs of EntanglementLib prioritize 'security' for 'military-grade security' and 'establishing secure infrastructure in financial and large-scale enterprise environments' as the foremost principle. EntanglementLib is implemented to fundamentally prevent potential security vulnerabilities and ensure data integrity. The second core value is 'stability'. To ensure predictable and consistent performance, Rust native operations are applied to maximize memory efficiency very safely, and a systematic error (or exception) handling mechanism is equipped.
-
-## Strengths
-
-EntanglementLib possesses the following strengths:
-
-1. **Anti-Data Remanence and Memory Erasure**
- - We accurately identified that Java's memory management model, the Garbage Collector (GC), can be vulnerable to security threats, and equipped technology to technically overcome this.
- - For this technology, we designed it to safely erase data using the [`entlib-native` native library](https://github.com/Quant-Off/entlib-native).
- - We provide a [Data Container feature](https://github.com/Quant-Off/entanglementlib/blob/master/src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java) to safely manage all sensitive information.
-2. **Compliance with Latest PQC Standards**
- - It supports the latest algorithms standardized by NIST such as ML-KEM, ML-DSA, and SLH-DSA according to FIPS 203, 204, and 205, as well as classical algorithms like AES, ARIA, and ChaCha20, processing them natively to enable secure operations.
-3. **Architecture and Design Patterns**
- - Factory patterns and interfaces are clearly separated. [Cryptographic algorithms can be called and used strategically](https://github.com/Quant-Off/entanglementlib/tree/master/src/main/java/space/qu4nt/entanglementlib/security/crypto).
-4. **Exception Handling**
- - We ensured that standard exceptions are not thrown as is. They are wrapped in EntanglementLib's custom exception classes to provide clear context.
- - If a problem is discovered from a security perspective, the system is designed to suggest a solution and defend against concerning attacks (hacking).
-
-## Key Features
-
-EntanglementLib includes powerful encryption features and utilities for improving development productivity.
-
-| Module | Technical Specification |
-| :--- | :--- |
-| `security.crypto` | Provides Post-Quantum Cryptography (PQC) and modern encryption algorithms.
- **PQC (NIST Standards)**: Supports `ML-DSA` (Digital Signature Algorithm), `ML-KEM` (Key Encapsulation Mechanism), and `SLH-DSA` to build a security system responding to the quantum computing era.
- **Symmetric Key Encryption**: Supports `ChaCha20-Poly1305` AEAD (Authenticated Encryption with Associated Data) to provide high levels of confidentiality and integrity simultaneously.
- **High-Performance Computing**: Major algorithms utilize SIMD (Single Instruction, Multiple Data) instruction sets to maximize parallel processing and optimize calculation speed.
- **Strategy Pattern**: Algorithms can be flexibly registered and used through `RegistrableStrategy` and `EntLibCryptoRegistry`. |
-| `security.communication` | Provides session and TLS-related functions for secure communication.
- **Session Management**: Creates and manages secure sessions through `Session`, `Participant`, `SessionConfig`, etc.
- **TLS Support**: Builds TLS-based secure communication channels through `Server`, `ServerConfig`, etc. |
-| `entlibnative` | Handles linkage with native libraries.
- **Data Protection**: Safely manages and erases sensitive data through `SensitiveDataContainer`.
- **Native Linkage**: Loads and uses high-performance native libraries written in Rust, etc., through `NativeLinkerManager`. |
-| `util` | Provides high-performance utilities considering security and development convenience.
- **I/O and Chunk Processing**: Efficiently processes large files and data through `EntFile`, `ChunkProcessor`, etc.
- **Security Utilities**: Safely handles passwords and sensitive strings through `Password`, `SecureCharBuffer`, etc.
- **Others**: Provides various utility classes such as `Hex`, `Hash`, `Async`, etc. |
-| `exception` | Provides a systematic exception hierarchy for stable error handling.
- **Security Exceptions**: Clearly handles security-related errors through `EntLibSecureException` and its subclasses.
- **Encryption Exceptions**: Manages errors occurring during the encryption process by subdividing them through `EntLibCryptoException`, etc.
- **Fatal Errors**: Defines fatal system errors through `EntLibError` and its subclasses. |
-
-## Technical Specifications
-
-Technical specifications on how EntanglementLib interacts with native libraries and how security operations are performed natively are written [here](TECHNICAL.md).
-
-## Benchmarking Records
-
-We are performing bridge benchmarking for various operations, such as nanosecond delays occurring when calling Rust native functions using the FFM API in EntanglementLib. These tasks are directly related to performance and security and play an important role in creating optimal code.
-
-EntanglementLib's benchmarking is conducted in the [`native-benchmark`](native-benchmark) Rust project, and benchmarking records are written [here](NATIVE_BENCHMARK.md).
+All designs of EntanglementLib prioritize security for 'military security' and 'establishing secure infrastructure in financial and large enterprise environments'. EntanglementLib is implemented to fundamentally prevent potential security vulnerabilities and ensure data integrity. The second core value is stability. To ensure predictable and consistent performance, Rust native operations are applied to maximize memory efficiency very safely, and a systematic error (or exception) handling mechanism is equipped.
-## Getting Started
+## Technology
-### 1. Requirements
+EntanglementLib performs all security operations through [Rust-based native](https://github.com/Quant-Off/entlib-native). Native destroys all security weaknesses that can be triggered in the cleaning mechanism of the Garbage Collector (GC) due to `heap` memory allocation. It receives sensitive data from the Java side as `off-heap` to perform tasks, and immediately and safely erases the data of the pointer through the caller or callee pattern.
-EntanglementLib was developed in the following environment. Detailed testing for other versions has not been conducted.
+When interacting with Native from the Java side, simply JNI (Java Native Interface) functions are not used. The core technology is the Linker, FFM API (Foreign Function & Memory API), which is an advanced native call function based on [JEP 389](https://openjdk.org/jeps/389) and [JEP 454](https://openjdk.org/jeps/454) improvements, and on the Native side, it is linked via FFI (Foreign Function Interface) through encapsulated logic.
-- Java 25
-- Gradle 9.2.0
+> [!TIP]
+> If you are curious about the background and overview of Native, please refer to [here](https://qu4nt.space/projects/entlib-native).
+>
+> Or if you are curious about the background of EntanglementLib, please refer to [here](https://qu4nt.space/projects/entanglementlib).
-EntanglementLib uses Java 25 or higher to stably use the algorithms included in NIST FIPS 203, 204, and 205 standardization.
+Within this library, user data is not managed as byte arrays (`byte[]`) or character arrays (`char[]`). These types give initiative to `heap` memory, and furthermore to GC. Instead of this primitive use, you can use the `SensitiveDataContainer` object. This object takes over the ownership of sensitive data and passes it safely to Native to help process tasks safely and efficiently. More specifically, this object acquires resources at the time of instantiation and releases resources at the time of `close()` call, similar to [Rust's RAII (Resource Acquisition Is Initialization) pattern](https://doc.rust-lang.org/rust-by-example/scope/raii.html). This concept may be quite unique in Java.
-### 2. Environment Variable Configuration
+## Multi-module
-The following environment variable settings are required for the normal operation of `EntanglementLib`.
+EntanglementLib is now a multi-module project. The role of each module is divided into utilities including tasks and practical annotations, security, and various convenience tools. Annotation and core modules are essentially used in security modules, but security modules are never used in other modules.
-| Variable Name | Description | Default | Required |
-| :--- | :--- | :--- | :--- |
-| `ENTLIB_NATIVE_BIN` | Specifies the directory path containing the native library binaries. | - | Required |
-| `ENTANGLEMENT_HOME_DIR` | Specifies the directory path where resources used inside EntanglementLib will be stored. | - | Required |
+| Module | Function |
+|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------|
+| `security` | Core security module. Provides logic for interaction with Native and various security functions linked via FFI. |
+| `core` | Provides utility functions managing exceptions, internationalization and asynchronous, chunk operations, strings, and data structures. |
+| `annotations` | Includes annotations for easy code design and improving user's code understanding complexity. |
-```bash
-# Example: Linux/macOS
-export ENTLIB_NATIVE_BIN="/path/to/entlib-native/release"
-export ENTANGLEMENT_HOME_DIR="/path/to/entanglementlib"
-```
-```bash
-# Example: Windows
-setx ENTLIB_NATIVE_BIN "C:\path\to\entlib-native\release"
-setx ENTANGLEMENT_HOME_DIR "C:\path\to\entanglementlib"
-```
+## Technical Specification
-### 3. Clone Repository or Use Maven Repository
+Detailed technical specifications for EntanglementLib are being written.
-You can clone the repository via the following command:
-
-```shell
-$ git clone https://github.com/Quant-Off/entanglementlib.git
-$ cd entanglementlib
-```
-
-If you want to register it as a dependency, you can simply use the `Maven` repository.
-
-#### Maven Project
+## Benchmarking Records
-```xml
-
-
- space.qu4nt.entanglementlib
- entanglementlib
- 1.0.0
-
-
-```
+We are performing bridge benchmarking for various operations such as nanosecond delays occurring when calling Rust native functions using FFM API in EntanglementLib. This work is directly related to performance and security, and plays an important role in creating optimal code.
-#### Gradle Project
+Many benchmarking tasks are scheduled in this alpha version. We plan to proceed with this work through JMH (Java Microbenchmark Harness), and we will organize it in a new document as soon as it is completed.
-```kotlin
-repositories {
- mavenCentral()
-}
+## Contribution
-dependencies {
- implementaion("space.qu4nt.entanglementlib:entanglementlib:1.0.0")
- // or Groovy
- // implementaion 'space.qu4nt.entanglementlib:entanglementlib:1.0.0'
-}
-```
+This project is currently `Alpha` version, and is still lacking a lot. We are already ready to actively receive your feedback. EntanglementLib is developed not only to provide PQC algorithms, but also to be used as a competent tool that systematically monitors infrastructure security in the user environment and finds solutions for users. From the latest release, we will focus strongly on this belief.
## TODO
-This project is still lacking in many areas. EntanglementLib aims to clarify the following TODOs so that it can be used in financial and security infrastructure production in the future.
+EntanglementLib wants to clarify the following TODOs so that it can be used in financial and security infrastructure production in the future.
-- [ ] **Provider Fluidity and Factory Optimization**
- - We need to modify it so that Java's default provider can be used according to the user's choice. And we need to optimize the factories provided in most classes, including the `InternalFactory` class.
-- [ ] **Overall Design Pattern Optimization**
- - The current code has many dirty parts, enough to be called spaghetti code. We need to modify it to write pleasant code like my room.
-- [ ] **JPMS Application**
- - Once secure encapsulation and consistent call (or usage) patterns are completed, we intend to manage encapsulated packages as modules through JPMS.
-- [ ] **Remove `BouncyCastle` Dependency**
- - From the `1.1.0` release onwards, we intend to minimize `BC` dependency and eventually remove it. Currently, classical algorithms like AES, ARIA, and ChaCha20 still depend on `BC`. To solidify EntanglementLib's security philosophy, we have decided to perform all cryptographic operations in `entlib-native`.
-- [ ] **`i18n` Update**
- - We missed a lot of multi-language support while developing the latest release. We need to modify it to support logging for each language according to configuration settings.
+- [ ] Add TLS communication logic
+- [ ] Prepare and perform complex verification tasks
+- [ ] Custom Exception Optimization
+- [ ] Apply JPMS
+ - Once secure encapsulation and consistent call (or usage) patterns are completed, we intend to manage encapsulated packages as modules through JPMS.
+- [ ] Minimize external dependencies
+ - Now, from the `1.1.0` release, we have minimized `BouncyCastle` dependencies and finally succeeded in removing them. Dependencies that provide some useful tools needed for current code writing still remain, but these will also be minimized eventually.
+- [ ] Update `i18n`
+ - While performing the latest release development, we missed a lot of multilingual support. We need to modify it to support logging for each language according to configuration settings.
### Resolved
-- [X] **Conflict between Java Module System (JPMS) and Reflection**
- - `KeyDestroyHelper` uses `Field.setAccessible(true)` to modify `private` fields in `BouncyCastle` internals or the Java standard library. Due to the strong encapsulation policy since Java 17, there is a very high probability that an `InaccessibleObjectException` will occur without the `--add-opens` JVM option at runtime.
+- [X] Conflict between Java Module System (JPMS) and Reflection
+ - In `KeyDestroyHelper`, `Field.setAccessible(true)` is used to modify `private` fields inside `BouncyCastle` or Java standard libraries. Due to the strong encapsulation policy since Java 17, there is a very high probability that `InaccessibleObjectException` will occur without the `--add-opens` JVM option at runtime.
- You can bypass JPMS security warnings by adding the JVM option `--add-opens java.base/java.security=ALL-UNNAMED`.
- - **Resolution**: We didn't worry too much about solving this problem. Because we decided to use the `BC Lightweight API` from the `1.1.0` release. The first goal was to avoid some constraints of the existing JCA/JCE with low-level access. In other words, access via reflection is still indispensable. It means we might not be able to respond flexibly when generating keys or calling internal encryption engines. To solve these complex problems, we introduced the `entlib-native` native library and intend to focus on minimizing EntanglementLib's `BC` dependency.
-- [X] **Performance vs. Security Trade-off**
- - We are performing defensive copies (deep copy) for all I/O. In server environments that process large data in gigabytes or require high throughput (Tick Per Second, TPS), frequent memory allocation and Garbage Collector load can cause performance degradation. Even looking at existing algorithm classes, binding data to instances is visible.
- - **Resolution**: We added the `entlib-native` native library and designed it so that all memory-related operations are handled on the `Rust` side. Now, on the `Java` side, we just need to erase sensitive data like byte arrays that are simply received. `Rust` will reliably handle memory operations in the background.
-- [X] **Random Number and Nonce Management**
- - `ChaCha20Poly1305` uses `InternalFactory.getSafeRandom()` to generate the nonce value `Nonce`. If the `Nonce` is reused with the same key, the security of `ChaCha20Poly1305` completely collapses.
- - **Resolution**: This problem was also solved with the `entlib-native` native library. Now, a `ChaCha20`-based `CSPRNG` is created on the `Rust` side and used only on the `Rust` side. In other words, all cryptographic operations are now performed only by `Rust`!
-- [X] **Writing Technical Specifications for Security Features**
- - **Resolution**: Important security-related features of EntanglementLib have been written in a [separate document](TECHNICAL.md).
-
-## Contribution
-
-In the early versions, EntanglementLib had really vague goals like 'BouncyCastle wrapper', 'half-hearted data erasure', etc. We spent quite a lot of time thinking deeply about this part and re-established EntanglementLib's unique goals.
-
-Now, EntanglementLib is developed not just to provide PQC algorithms, but to be used as a competent tool that systematically monitors infrastructure security in the user's environment and finds solutions for the user. From the latest release, we will focus strongly on this belief.
-
-Therefore... we need the power of many developers to complete the ultimate goal of EntanglementLib. Please leave feedback on the code at any time. It is a very, very big help to the Quant team!
-
-## Alpha
-
-Currently, EntanglementLib has released `1.1.0-Alpha` quite hastily to quickly reflect the accumulated updates. The stable version of this version is scheduled to be modified and reflected as the `1.1.0` official release. Since it is a one-person development, there is a problem that the speed is slow, but not much is left until all modifications.
+ - **Resolution**: We didn't worry too much to solve this problem. Because we decided to use `BC Lightweight API` from the `1.1.0` release. The first goal was to avoid some restrictions of existing JCA/JCE with low-level access. In other words, access through reflection is still inevitable. It means that we may not be able to respond flexibly when generating keys or calling internal encryption engines. To solve these complex problems, we introduced the `entlib-native` native library, and we intend to focus on minimizing `BC` dependencies of EntanglementLib.
+- [X] Performance vs Security Trade-off
+ - Defensive copy (deep copy) is performed for all inputs and outputs. In server environments that process large data in gigabytes or require high throughput (Tick Per Second, TPS), frequent memory allocation and garbage collector load can cause performance degradation. Just looking at existing algorithm classes, you can see data binding to instances.
+ - **Resolution**: By adding the `entlib-native` native library, we designed all memory-related operations to be processed on the `Rust` side. Now, on the `Java` side, we simply need to erase sensitive data such as byte arrays received purely. `Rust` will reliably take care of memory operations in the back.
+- [X] Random Number and Nonce Management
+ - In `ChaCha20Poly1305`, `InternalFactory.getSafeRandom()` is used to generate nonce value `Nonce`. If `Nonce` is reused with the same key, the security of `ChaCha20Poly1305` collapses completely.
+ - **Resolution**: This problem was also solved with the `entlib-native` native library. Now, `ChaCha20`-based `CSPRNG` is created on the `Rust` side, and used only on the `Rust` side. In other words, all cryptographic operations are now performed only by `Rust`!
+- [X] Writing Technical Specifications for Security Functions
+ - **Resolution**: Important security-related functions of EntanglementLib have been written in a [separate document](TECHNICAL.md).
## License
-This project follows the `PolyForm Noncommercial License 1.0.0`. Please note that due to the simultaneous management of `entlib-native` within this project, the license is sometimes incorrectly reflected as `MIT`, but it still follows the `PolyForm` license. For more details on this license, please refer to the [LICENSE](LICENSE) file.
+This project follows `PolyForm Noncommercial License 1.0.0`. Please note that although the license is sometimes incorrectly reflected as `MIT` due to simultaneous management of `entlib-native` within this project, it still follows the `PolyForm` license. For more details on this license, please refer to the [LICENSE](LICENSE) file.
---
-# Changelog
+# Changes
Changes can be found in the [CHANGE.md](CHANGE.md) document. This document will be added when the `1.1.0` release is published.
\ No newline at end of file
diff --git a/annotation-processor/build.gradle.kts b/annotation-processor/build.gradle.kts
new file mode 100644
index 0000000..48339bb
--- /dev/null
+++ b/annotation-processor/build.gradle.kts
@@ -0,0 +1,3 @@
+dependencies {
+ implementation(project(":annotations"))
+}
\ No newline at end of file
diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts
new file mode 100644
index 0000000..0ce6a16
--- /dev/null
+++ b/annotations/build.gradle.kts
@@ -0,0 +1,3 @@
+dependencies {
+
+}
\ No newline at end of file
diff --git a/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java
new file mode 100644
index 0000000..3462f0a
--- /dev/null
+++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java
@@ -0,0 +1,29 @@
+package space.qu4nt.entanglementlib.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/// 보안적 책임은 호출자에게 있음을 알리는 어노테이션입니다. 호출자가 해당 어노테이션이
+/// 사용된 멤버 사용 시, 작업 종료 후 반드시 보안 작업이 필요함을 의미합니다.
+///
+/// 예를 들어, 이 어노테이션이 (복사본이 아닌) 원본 데이터를 반환하는 메소드에 사용되었고
+/// 해당 메소드를 사용하고자 하는 경우, 반환받은 데이터를 소거해야 함을 의미합니다.
+///
+/// 또는 이 어노테이션이 사용된 멤버는 네이티브 측의 "호출자 할당" 패턴을 사용한 경우로,
+/// 작업이 종료됨과 동시에 네이티브에 할당 해제 함수를 호출해야 함을 의미합니다.
+///
+/// @author Q. T. Felix
+/// @since 1.1.0
+@Documented
+@Target(ElementType.TYPE_USE)
+public @interface CallerResponsibility {
+
+ /**
+ * 책임 전가의 사유 또는 설명를 정의합니다.
+ *
+ * @return 책임 전가 사유 또는 설명
+ */
+ String value() default "";
+
+}
\ No newline at end of file
diff --git a/src/main/java/space/qu4nt/entanglementlib/ExternalPattern.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/ExternalPattern.java
similarity index 62%
rename from src/main/java/space/qu4nt/entanglementlib/ExternalPattern.java
rename to annotations/src/main/java/space/qu4nt/entanglementlib/annotations/ExternalPattern.java
index 804884f..423d64e 100644
--- a/src/main/java/space/qu4nt/entanglementlib/ExternalPattern.java
+++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/ExternalPattern.java
@@ -1,16 +1,11 @@
-/*
- * Copyright © 2025-2026 Quant.
- * Under License "PolyForm Noncommercial License 1.0.0".
- */
-
-package space.qu4nt.entanglementlib;
+package space.qu4nt.entanglementlib.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* 반드시 외부에서만 사용됨을 알리는 마커 어노테이션입니다.
- * {@link InternalFactory} 객체 부트스트랩 시, 내부(internal)에서 사용되는 멤버와
+ * 라이브러리 부트스트랩, 또는 기타 상황 속 내부(internal)에서 사용되는 멤버와
* 외부(external)에서 사용되는 멤버는 다르다는 것을 명확히 하기 위해 사용됩니다.
*
* 이 어노테이션은 타입 레벨에 사용하지 마세요. 혼동이 생길 수 있습니다.
@@ -19,6 +14,4 @@
* @since 1.1.0
*/
@Target(ElementType.TYPE_USE)
-public @interface ExternalPattern {
-
-}
+public @interface ExternalPattern { }
diff --git a/src/main/java/space/qu4nt/entanglementlib/Unsafe.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Unsafe.java
similarity index 79%
rename from src/main/java/space/qu4nt/entanglementlib/Unsafe.java
rename to annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Unsafe.java
index 50548da..1b417c1 100644
--- a/src/main/java/space/qu4nt/entanglementlib/Unsafe.java
+++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Unsafe.java
@@ -1,12 +1,4 @@
-/*
- * Copyright © 2025-2026 Quant.
- * Under License "PolyForm Noncommercial License 1.0.0".
- */
-
-package space.qu4nt.entanglementlib;
-
-import com.quant.quantregular.annotations.QuantTypeOwner;
-import com.quant.quantregular.annotations.Quanters;
+package space.qu4nt.entanglementlib.annotations;
import java.lang.annotation.Documented;
@@ -16,7 +8,7 @@
///
/// 이 어노테이션이 사용된 요소(멤버)가 본질적으로 안전하지 않으며 보안 위험을 초래할 수 있음을 나타내기 위한
/// 어노테이션입니다.
-///
+///
/// 구체적으로 이 어노테이션은 사용된 메소드, 필드 또는 타입 등의 멤버가 다음 이유 중 하나로 인해 안전하지
/// 않음을 나타냅니다.
///
@@ -26,22 +18,25 @@
/// - 보안 취약점 발견
///
/// 이 API를 사용하려면 기본 구현 및 잠재적인 부작용에 대한 깊은 이해가 필요합니다.
-///
+///
/// # Important
///
/// 이 요소를 부적절하게 사용하면 `메모리 손상`, `데이터 유출` 또는 **임의 코드 실행을 포함하되 이에 국한되지 않는
/// 심각한 보안 취약점이 발생**할 수 있습니다. 또한 정의되지 않은 동작이나 애플리케이션 불안정을 초래할 수도 있습니다.
-///
+///
/// 사용자는 절대적으로 필요한 경우에만 이 요소를 사용해야 하며, 위험을 완화하기 위해 적절한 보안 조치와
/// 검증 로직이 마련되어 있는지 확인해야 합니다.
///
/// # Note
///
-/// 이 어노테이션이 사용된 멤버는 `# Security` 헤더에 `Unsafe` 근거가 기록되어야 합니다.
+/// 이 어노테이션이 사용된 멤버는 `# Unsafe` 헤더에 근거가 기록되어야 합니다.
///
/// @author Q. T. Felix
/// @since 1.1.0
@Documented
-@QuantTypeOwner(Quanters.Q_T_FELIX)
public @interface Unsafe {
+
+ /// 명확한 `unsafe` 사유입니다.
+ /// @return 명확한 `unsafe` 사유
+ String value() default "";
}
diff --git a/build.gradle.kts b/build.gradle.kts
index 6ac1a06..ac994f7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,9 +4,8 @@
*/
plugins {
- id("java")
+ java
id("com.vanniktech.maven.publish") version "0.28.0"
- id("me.champeau.jmh") version "0.7.3"
}
val commonGroupId = project.findProperty("commonGroupId") as? String ?: "space.qu4nt"
@@ -14,210 +13,144 @@ val quantPublicDir = project.findProperty("quantPublicDir") as? String
?: layout.buildDirectory.dir("dummy-resources").get().asFile.absolutePath
val lombokVersion = "org.projectlombok:lombok:1.18.42"
-val bouncyCastleVer = "1.83"
val entLibVersion = "1.1.2-Alpha3"
-group = commonGroupId
-version = entLibVersion
-
-sourceSets {
- main {
- java {
- srcDirs("src/main/java")
- }
- resources {
- srcDirs("src/main/resources")
-
- if (quantPublicDir.isNotEmpty()) {
- val extraResourceDir = File("${quantPublicDir}/entanglementlib")
- if (extraResourceDir.exists()) {
- srcDir(extraResourceDir)
- } else {
- logger.warn("Warning: External resource directory not found: $extraResourceDir. Skipping...")
- }
- }
- }
- }
+allprojects {
+ group = "{$commonGroupId}.entanglementlib"
+ version = entLibVersion
+}
- test {
- java {
- srcDirs("src/test/java")
- }
- resources {
- srcDirs("src/test/resources")
+subprojects {
+ apply(plugin = "java")
+ apply(plugin = "com.vanniktech.maven.publish")
- if (quantPublicDir.isNotEmpty()) {
- val extraTestResourceDir = File("${quantPublicDir}/entanglementlib-test")
- if (extraTestResourceDir.exists()) {
- srcDir(extraTestResourceDir)
- }
- }
+ java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(25))
}
+ sourceCompatibility = JavaVersion.VERSION_25
+ targetCompatibility = JavaVersion.VERSION_25
}
- named("jmh") {
- java {
- srcDirs("src/benchmark/java")
- }
- resources {
- srcDirs("src/benchmark/resources")
- }
+ repositories {
+ mavenCentral()
}
-}
-java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(25))
+ dependencies {
+ // JetBrains Annotations
+ // https://mvnrepository.com/artifact/org.jetbrains/annotations
+ implementation("org.jetbrains:annotations:26.0.2-1")
+
+ // Logging
+ // https://mvnrepository.com/artifact/org.slf4j/slf4j-api
+ implementation("org.slf4j:slf4j-api:2.0.17")
+ // bridger
+ // https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j
+ implementation("org.slf4j:jul-to-slf4j:2.0.17")
+ // Logging Provider (Logback)
+ // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
+ implementation("ch.qos.logback:logback-classic:1.5.26")
+
+ // Lombok
+ // https://mvnrepository.com/artifact/org.projectlombok/lombok
+ implementation(lombokVersion)
+ annotationProcessor(lombokVersion)
+
+ // Tests JUnit 5
+ testImplementation(platform("org.junit:junit-bom:5.10.0"))
+ testImplementation("org.junit.jupiter:junit-jupiter")
+ testImplementation("org.assertj:assertj-core:3.27.7")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+ testAnnotationProcessor(lombokVersion)
}
- sourceCompatibility = JavaVersion.VERSION_25
- targetCompatibility = JavaVersion.VERSION_25
-}
-repositories {
- mavenCentral()
-}
-
-dependencies {
- // Quant-regular
- implementation("space.qu4nt.quant-regular:annotations:1.0.0")
-
- // JetBrains Annotations
- // https://mvnrepository.com/artifact/org.jetbrains/annotations
- implementation("org.jetbrains:annotations:26.0.2-1")
-
- // Logging
- // https://mvnrepository.com/artifact/org.slf4j/slf4j-api
- implementation("org.slf4j:slf4j-api:2.0.17")
- // bridger
- // https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j
- implementation("org.slf4j:jul-to-slf4j:2.0.17")
- // Logging Provider (Logback)
- // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
- implementation("ch.qos.logback:logback-classic:1.5.26")
-
- // Lombok
- // https://mvnrepository.com/artifact/org.projectlombok/lombok
- implementation(lombokVersion)
- annotationProcessor(lombokVersion)
-
- // https://mvnrepository.com/artifact/org.yaml/snakeyaml
- implementation("org.yaml:snakeyaml:2.5")
-
- // Jackson
- // https://mvnrepository.com/artifact/tools.jackson.core/jackson-databind
- implementation("tools.jackson.core:jackson-databind:3.0.2")
- // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
- implementation("com.fasterxml.jackson.core:jackson-annotations:3.0-rc5")
-
- // BouncyCastle
- implementation("org.bouncycastle:bcprov-jdk18on:${bouncyCastleVer}")
- implementation("org.bouncycastle:bcutil-jdk18on:${bouncyCastleVer}")
- implementation("org.bouncycastle:bcpkix-jdk18on:${bouncyCastleVer}")
- implementation("org.bouncycastle:bctls-jdk18on:${bouncyCastleVer}")
-
- // Tests JUnit 5
- testImplementation(platform("org.junit:junit-bom:5.10.0"))
- testImplementation("org.junit.jupiter:junit-jupiter")
- testImplementation("org.assertj:assertj-core:3.27.7")
- testRuntimeOnly("org.junit.platform:junit-platform-launcher")
- testAnnotationProcessor(lombokVersion)
-
- // JMH
- jmh("org.openjdk.jmh:jmh-core:1.37")
- jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37")
- // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core
- testImplementation("org.openjdk.jmh:jmh-core:1.37")
- // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess
- testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
- jmhAnnotationProcessor(lombokVersion)
-}
-
-tasks.test {
- useJUnitPlatform()
-}
+ sourceSets {
+ main {
+ java {
+ srcDirs("src/main/java")
+ }
+ resources {
+ srcDirs("src/main/resources")
+
+ if (quantPublicDir.isNotEmpty()) {
+ val extraResourceDir = File("${quantPublicDir}/entanglementlib")
+ if (extraResourceDir.exists()) {
+ srcDir(extraResourceDir)
+ } else {
+ logger.warn("Warning: External resource directory not found: $extraResourceDir. Skipping...")
+ }
+ }
+ }
+ }
-tasks.jar {
- from("entlib-native/dist") {
- into("native")
- include("linux/libentlib_native_aarch64.so", "linux/libentlib_native_x86_64.so",
- "macos/libentlib_native_aarch64.dylib", "macos/libentlib_native_universal.dylib", "macos/libentlib_native_x86_64.dylib",
- "windows/entlib_native_x86_64.dll")
+ test {
+ java {
+ srcDirs("src/test/java")
+ }
+ resources {
+ srcDirs("src/test/resources")
+
+ if (quantPublicDir.isNotEmpty()) {
+ val extraTestResourceDir = File("${quantPublicDir}/entanglementlib-test")
+ if (extraTestResourceDir.exists()) {
+ srcDir(extraTestResourceDir)
+ }
+ }
+ }
+ }
}
-}
-tasks.withType {
- duplicatesStrategy = DuplicatesStrategy.INCLUDE
-}
+ mavenPublishing {
+ signAllPublications()
-mavenPublishing {
- signAllPublications()
+ coordinates("${commonGroupId}.${project.name}", project.name, entLibVersion)
- coordinates("${commonGroupId}.${project.name}", project.name, entLibVersion)
+ pom {
+ name = project.name
+ description = "Quant EntanglementLib"
+ inceptionYear = "2025"
+ url = "https://github.com/Quant-Off/entanglementlib"
- pom {
- name = project.name
- description = "Quant EntanglementLib"
- inceptionYear = "2025"
- url = "https://github.com/Quant-Off/entanglementlib"
+ licenses {
+ license {
+ name = "PolyForm Noncommercial License 1.0.0"
+ url = "https://polyformproject.org/licenses/noncommercial/1.0.0/"
+ }
+ }
- licenses {
- license {
- name = "PolyForm Noncommercial License 1.0.0"
- url = "https://polyformproject.org/licenses/noncommercial/1.0.0/"
+ developers {
+ developer {
+ id = "qtfelix"
+ name = "Q. T. Felix"
+ url = "https://github.com/Quant-TheodoreFelix"
+ }
}
- }
- developers {
- developer {
- id = "qtfelix"
- name = "Q. T. Felix"
- url = "https://github.com/Quant-TheodoreFelix"
+ scm {
+ url = "https://github.com/Quant-Off/entanglementlib"
+ connection = "scm:git:git://github.com/Quant-Off/entanglementlib.git"
+ developerConnection = "scm:git:ssh://git@github.com/Quant-Off/entanglementlib.git"
}
}
- scm {
- url = "https://github.com/Quant-Off/entanglementlib"
- connection = "scm:git:git://github.com/Quant-Off/entanglementlib.git"
- developerConnection = "scm:git:ssh://git@github.com/Quant-Off/entanglementlib.git"
- }
+ configure(
+ com.vanniktech.maven.publish.JavaLibrary(
+ sourcesJar = true,
+ javadocJar = com.vanniktech.maven.publish.JavadocJar.Javadoc()
+ )
+ )
}
- configure(
- com.vanniktech.maven.publish.JavaLibrary(
- sourcesJar = true,
- javadocJar = com.vanniktech.maven.publish.JavadocJar.Javadoc()
- )
- )
-}
+ tasks.test {
+ useJUnitPlatform()
+ }
-//
-// JMH - start
-//
-jmh {
- jmhVersion.set("1.37")
-
- // 테스트 벤치마킹 클래스 등록
- includeTests.set(true)
- includes.set(listOf(".*_JMHBenchmark"))
-
- // 벤치마크 실행 시 필요한 jvm 인자 중앙 제어
- jvmArgs.set(listOf(
- "--enable-native-access=ALL-UNNAMED",
- "--enable-preview",
- "-Djava.library.path=${projectDir}/native-benchmark/target/debug",
- "-Xms2g", "-Xmx2g" // gc 간섭 최소화
- ))
- fork.set(1)
-
- // 결과 출력 포맷
- resultFormat.set("JSON")
-
- // 벤치마크 수행 옵션 (프로세스 및 반복 횟수)
- fork.set(1)
- warmupIterations.set(3)
- iterations.set(5)
-}
-//
-// JMH - end
-//
\ No newline at end of file
+ tasks.withType {
+ duplicatesStrategy = DuplicatesStrategy.INCLUDE
+ }
+
+ tasks.named("sourcesJar") {
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ }
+}
\ No newline at end of file
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
new file mode 100644
index 0000000..195d444
--- /dev/null
+++ b/core/build.gradle.kts
@@ -0,0 +1,12 @@
+dependencies {
+ implementation(project(":annotations"))
+
+ // Jackson
+ // https://mvnrepository.com/artifact/tools.jackson.core/jackson-databind
+ implementation("tools.jackson.core:jackson-databind:3.0.2")
+ // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
+ implementation("com.fasterxml.jackson.core:jackson-annotations:3.0-rc5")
+
+ // https://mvnrepository.com/artifact/org.yaml/snakeyaml
+ implementation("org.yaml:snakeyaml:2.5")
+}
\ No newline at end of file
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/EntanglementLibCoreContext.java b/core/src/main/java/space/qu4nt/entanglementlib/core/EntanglementLibCoreContext.java
new file mode 100644
index 0000000..ce1c30c
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/EntanglementLibCoreContext.java
@@ -0,0 +1,27 @@
+package space.qu4nt.entanglementlib.core;
+
+import lombok.Getter;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreIllegalArgumentException;
+import space.qu4nt.entanglementlib.core.i18n.EntanglementLibCoreI18n;
+
+import java.util.Locale;
+
+public final class EntanglementLibCoreContext {
+
+ @Getter
+ private static volatile boolean initialized = false;
+
+ private EntanglementLibCoreContext() {
+ throw new UnsupportedOperationException("cannot access");
+ }
+
+ public static synchronized void initialize(final @NotNull Locale locale, @Nullable String userResourceBasename)
+ throws ELIBCoreIllegalArgumentException {
+ if (!initialized) {
+ EntanglementLibCoreI18n.initialize(locale, userResourceBasename);
+ initialized = true;
+ }
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBException.java
new file mode 100644
index 0000000..04fb805
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBException.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2025-2026 Quant.
+ * Under License "PolyForm Noncommercial License 1.0.0".
+ */
+
+package space.qu4nt.entanglementlib.core.exception;
+
+import java.io.Serial;
+
+/// 얽힘 라이브러리 전반에서 사용되는 기본 예외 클래스입니다.
+///
+/// 이 클래스는 [Exception]을 상속받으며, 기본적으로 `Checked Exception`입니다.
+/// 확장하여 다양한 상황에 대해 예외 처리가 가능합니다.
+///
+/// @author Q. T. Felix
+/// @since 1.1.0
+public class ELIBException extends Exception {
+
+ @Serial
+ private static final long serialVersionUID = 2378593800480779310L;
+
+ /**
+ * 새로운 {@link ELIBException} 인스턴스를 생성하는 기본 생성자 메소드입니다.
+ */
+ public ELIBException() {
+ super();
+ }
+
+ /**
+ * 다국어 처리가 필요하지 않은 경우 이 인스턴스를 사용할 수 있습니다.
+ *
+ * @param message 메시지
+ */
+ public ELIBException(String message) {
+ super(message);
+ }
+
+ /**
+ * 발생한 예외만 넘기기 위해 이 인스턴스를 사용할 수 있습니다.
+ *
+ * @param cause 발생 예외
+ */
+ public ELIBException(Throwable cause) {
+ super(cause);
+ }
+
+ /// 다국어 처리가 필요하지 않으며, 발생한 예외를 넘기기 위해 이
+ /// 인스턴스를 사용할 수 있습니다.
+ ///
+ /// @param message 메시지
+ /// @param cause 발생 예외
+ public ELIBException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBUncheckedException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBUncheckedException.java
new file mode 100644
index 0000000..374eb99
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBUncheckedException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2025-2026 Quant.
+ * Under License "PolyForm Noncommercial License 1.0.0".
+ */
+
+package space.qu4nt.entanglementlib.core.exception;
+
+import java.io.Serial;
+
+/// 얽힘 라이브러리 전반에서 사용되는 기본 예외 클래스입니다.
+///
+/// 이 클래스는 [Exception]을 상속받으며, `Unchecked Exception`입니다.
+///
+/// @author Q. T. Felix
+/// @since 1.1.0
+public class ELIBUncheckedException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 2378593800480779310L;
+
+ /**
+ * 새로운 {@link ELIBUncheckedException} 인스턴스를 생성하는 기본 생성자 메소드입니다.
+ */
+ public ELIBUncheckedException() {
+ }
+
+ /**
+ * 다국어 처리가 필요하지 않은 경우 이 인스턴스를 사용할 수 있습니다.
+ *
+ * @param message 메시지
+ */
+ public ELIBUncheckedException(String message) {
+ super(message);
+ }
+
+ /**
+ * 발생한 예외만 넘기기 위해 이 인스턴스를 사용할 수 있습니다.
+ *
+ * @param cause 발생 예외
+ */
+ public ELIBUncheckedException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/ExceptionLogger.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ExceptionLogger.java
similarity index 96%
rename from src/main/java/space/qu4nt/entanglementlib/exception/ExceptionLogger.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/exception/ExceptionLogger.java
index 033b9e0..b0e4f89 100644
--- a/src/main/java/space/qu4nt/entanglementlib/exception/ExceptionLogger.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ExceptionLogger.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.exception;
+package space.qu4nt.entanglementlib.core.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreException.java
new file mode 100644
index 0000000..3e64b0d
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.core;
+
+import java.io.Serial;
+
+public class ELIBCoreException extends Exception {
+
+ @Serial
+ private static final long serialVersionUID = -8478889423877054012L;
+
+ public ELIBCoreException() {
+ }
+
+ public ELIBCoreException(String message) {
+ super(message);
+ }
+
+ public ELIBCoreException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBCoreException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBCoreException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreIllegalArgumentException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreIllegalArgumentException.java
new file mode 100644
index 0000000..5fcddd0
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreIllegalArgumentException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.core;
+
+import java.io.Serial;
+
+public class ELIBCoreIllegalArgumentException extends ELIBCoreException {
+
+ @Serial
+ private static final long serialVersionUID = 8726329909869726228L;
+
+ public ELIBCoreIllegalArgumentException() {
+ }
+
+ public ELIBCoreIllegalArgumentException(String message) {
+ super(message);
+ }
+
+ public ELIBCoreIllegalArgumentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBCoreIllegalArgumentException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBCoreIllegalArgumentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreUtilityException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreUtilityException.java
new file mode 100644
index 0000000..f1ff035
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreUtilityException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.core;
+
+import java.io.Serial;
+
+public class ELIBCoreUtilityException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = -3902892852757530511L;
+
+ public ELIBCoreUtilityException() {
+ }
+
+ public ELIBCoreUtilityException(String message) {
+ super(message);
+ }
+
+ public ELIBCoreUtilityException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBCoreUtilityException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBCoreUtilityException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityException.java
new file mode 100644
index 0000000..044f33d
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.security.checked;
+
+import java.io.Serial;
+
+public class ELIBSecurityException extends Exception {
+
+ @Serial
+ private static final long serialVersionUID = 7009027800432974319L;
+
+ public ELIBSecurityException() {
+ }
+
+ public ELIBSecurityException(String message) {
+ super(message);
+ }
+
+ public ELIBSecurityException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBSecurityException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBSecurityException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIOException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIOException.java
new file mode 100644
index 0000000..9010216
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIOException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.security.checked;
+
+import java.io.Serial;
+
+public class ELIBSecurityIOException extends ELIBSecurityException {
+
+ @Serial
+ private static final long serialVersionUID = -5611460691131894020L;
+
+ public ELIBSecurityIOException() {
+ }
+
+ public ELIBSecurityIOException(String message) {
+ super(message);
+ }
+
+ public ELIBSecurityIOException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBSecurityIOException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBSecurityIOException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIllegalStateException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIllegalStateException.java
new file mode 100644
index 0000000..8d6fc35
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIllegalStateException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.security.checked;
+
+import java.io.Serial;
+
+public class ELIBSecurityIllegalStateException extends ELIBSecurityException {
+
+ @Serial
+ private static final long serialVersionUID = -2918719188402645982L;
+
+ public ELIBSecurityIllegalStateException() {
+ }
+
+ public ELIBSecurityIllegalStateException(String message) {
+ super(message);
+ }
+
+ public ELIBSecurityIllegalStateException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBSecurityIllegalStateException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBSecurityIllegalStateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityProcessException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityProcessException.java
new file mode 100644
index 0000000..638704c
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityProcessException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.security.checked;
+
+import java.io.Serial;
+
+public class ELIBSecurityProcessException extends ELIBSecurityException {
+
+ @Serial
+ private static final long serialVersionUID = -4547118937218025817L;
+
+ public ELIBSecurityProcessException() {
+ }
+
+ public ELIBSecurityProcessException(String message) {
+ super(message);
+ }
+
+ public ELIBSecurityProcessException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBSecurityProcessException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBSecurityProcessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityCritical.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityCritical.java
new file mode 100644
index 0000000..4140a89
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityCritical.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.security.critical;
+
+import java.io.Serial;
+
+public class ELIBSecurityCritical extends Error {
+
+ @Serial
+ private static final long serialVersionUID = -6905370663633314089L;
+
+ public ELIBSecurityCritical() {
+ }
+
+ public ELIBSecurityCritical(String message) {
+ super(message);
+ }
+
+ public ELIBSecurityCritical(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBSecurityCritical(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBSecurityCritical(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityNativeCritical.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityNativeCritical.java
new file mode 100644
index 0000000..9f62306
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityNativeCritical.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.security.critical;
+
+import java.io.Serial;
+
+public class ELIBSecurityNativeCritical extends ELIBSecurityCritical {
+
+ @Serial
+ private static final long serialVersionUID = -7557337159377165891L;
+
+ public ELIBSecurityNativeCritical() {
+ }
+
+ public ELIBSecurityNativeCritical(String message) {
+ super(message);
+ }
+
+ public ELIBSecurityNativeCritical(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBSecurityNativeCritical(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBSecurityNativeCritical(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/unchecked/ELIBSecurityIllegalArgumentException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/unchecked/ELIBSecurityIllegalArgumentException.java
new file mode 100644
index 0000000..1dd7663
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/unchecked/ELIBSecurityIllegalArgumentException.java
@@ -0,0 +1,28 @@
+package space.qu4nt.entanglementlib.core.exception.security.unchecked;
+
+import java.io.Serial;
+
+public class ELIBSecurityIllegalArgumentException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 3401738922121497475L;
+
+ public ELIBSecurityIllegalArgumentException() {
+ }
+
+ public ELIBSecurityIllegalArgumentException(String message) {
+ super(message);
+ }
+
+ public ELIBSecurityIllegalArgumentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ELIBSecurityIllegalArgumentException(Throwable cause) {
+ super(cause);
+ }
+
+ public ELIBSecurityIllegalArgumentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/i18n/EntanglementLibCoreI18n.java b/core/src/main/java/space/qu4nt/entanglementlib/core/i18n/EntanglementLibCoreI18n.java
new file mode 100644
index 0000000..36f16f2
--- /dev/null
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/i18n/EntanglementLibCoreI18n.java
@@ -0,0 +1,42 @@
+package space.qu4nt.entanglementlib.core.i18n;
+
+import lombok.Getter;
+import org.jetbrains.annotations.Nullable;
+import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreIllegalArgumentException;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.Objects;
+import java.util.ResourceBundle;
+
+public class EntanglementLibCoreI18n {
+
+ private static final String SYSTEM_DEFAULT_BASENAME = "entanglementlib-messages";
+
+ @Getter
+ private static Locale locale;
+ @Getter
+ private static ResourceBundle coreDefaultResourceBundle;
+ @Getter
+ private static @Nullable ResourceBundle userResourceBundle;
+
+ public static synchronized void initialize(final Locale locale, @Nullable String userResourceBasename) throws ELIBCoreIllegalArgumentException {
+ EntanglementLibCoreI18n.locale = Objects.requireNonNull(locale, "locale is null");
+ // getLanguage() => ISO 639 alpha2
+ try {
+ coreDefaultResourceBundle = ResourceBundle.getBundle(SYSTEM_DEFAULT_BASENAME, locale);
+ } catch (MissingResourceException e) { // 시스템 기본 국제화 파일을 찾을 수 없음
+ throw new ELIBCoreIllegalArgumentException("Could not find 'system default' i18n(" + locale.getLanguage() + ") file");
+ }
+
+ // 사용자 지정된 베이스네임 있으면 사용
+ if (userResourceBasename != null && !userResourceBasename.isBlank()) {
+ try {
+ userResourceBundle = ResourceBundle.getBundle(userResourceBasename, locale);
+ } catch (MissingResourceException e) { // 사용자 지정 국제화 파일을 찾을 수 없음
+ throw new ELIBCoreIllegalArgumentException("Could not find custom i18n(" + locale.getLanguage() + ") file '" + userResourceBasename + "'");
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/Async.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Async.java
similarity index 94%
rename from src/main/java/space/qu4nt/entanglementlib/util/Async.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/Async.java
index 4a3ed2f..deb2a7e 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/Async.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Async.java
@@ -3,9 +3,9 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util;
+package space.qu4nt.entanglementlib.core.util;
-import space.qu4nt.entanglementlib.exception.util.EntLibUtilityException;
+import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreUtilityException;
import java.util.List;
import java.util.concurrent.*;
@@ -67,7 +67,7 @@ public static CompletableFuture> runAsyncTaskList(List>
return future.join();
} catch (CompletionException e) {
// 개별 작업에서 발생한 예외를 감싸서 재처리
- throw new EntLibUtilityException(Async.class, "future-join-in-exc", e);
+ throw new ELIBCoreUtilityException("비동기 작업 중 예외가 발생했습니다!");
}
}).collect(Collectors.toList())
);
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/Nill.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Nill.java
similarity index 94%
rename from src/main/java/space/qu4nt/entanglementlib/util/Nill.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/Nill.java
index 5937ebb..29d70ee 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/Nill.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Nill.java
@@ -3,7 +3,9 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util;
+package space.qu4nt.entanglementlib.core.util;
+
+import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
@@ -86,7 +88,7 @@ public static void ifSo(final T obj, final Runnable runnable) {
* @param 타입 파라미터
* @return 결과
*/
- public static T nullDef(final T obj, final Supplier supplier) {
+ public static @NotNull T nullDef(final T obj, final @NotNull Supplier supplier) {
if (isNull(obj))
return supplier.get();
return obj;
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/StringUtil.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/StringUtil.java
similarity index 97%
rename from src/main/java/space/qu4nt/entanglementlib/util/StringUtil.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/StringUtil.java
index 93e9e6d..739cd2c 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/StringUtil.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/StringUtil.java
@@ -3,9 +3,10 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util;
+package space.qu4nt.entanglementlib.core.util;
-import space.qu4nt.entanglementlib.exception.util.EntLibUtilityIllegalArgumentException;
+import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreIllegalArgumentException;
+import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreUtilityException;
import java.util.Objects;
import java.util.UUID;
@@ -61,7 +62,7 @@ public static String replace(String source, String... rep) {
if (source == null)
return null;
if (rep.length % 2 != 0)
- throw new EntLibUtilityIllegalArgumentException(StringUtil.class, "replace-arg-not-even-exc");
+ throw new ELIBCoreUtilityException("replace-arg-not-even-exc");
for (int i = 0; i < rep.length; i += 2)
source = replace(source, rep[i], rep[i + 1]);
return source;
@@ -242,7 +243,7 @@ public static String truncateMiddle(String input, int maxLength, int prefixLengt
return "";
}
if (maxLength <= 0 || prefixLength < 0 || suffixLength < 0)
- throw new EntLibUtilityIllegalArgumentException(StringUtil.class, "truncate-length-exc");
+ throw new ELIBCoreUtilityException("truncate-length-exc");
int inputLength = input.length();
if (inputLength <= maxLength) {
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ByteArrayChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ByteArrayChunkProcessor.java
similarity index 98%
rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/ByteArrayChunkProcessor.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ByteArrayChunkProcessor.java
index 7185232..9c6f7d7 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ByteArrayChunkProcessor.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ByteArrayChunkProcessor.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.chunk;
+package space.qu4nt.entanglementlib.core.util.chunk;
import lombok.extern.slf4j.Slf4j;
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ChunkProcessor.java
similarity index 93%
rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/ChunkProcessor.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ChunkProcessor.java
index 6db0c3d..5209acf 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ChunkProcessor.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ChunkProcessor.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.chunk;
+package space.qu4nt.entanglementlib.core.util.chunk;
/**
* 대용량 데이터를 청크 단위로 처리할 때 사용되는 함수형 인터페이스입니다.
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/FileChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/FileChunkProcessor.java
similarity index 99%
rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/FileChunkProcessor.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/FileChunkProcessor.java
index d434337..e666ab5 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/FileChunkProcessor.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/FileChunkProcessor.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.chunk;
+package space.qu4nt.entanglementlib.core.util.chunk;
import lombok.extern.slf4j.Slf4j;
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/StringChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/StringChunkProcessor.java
similarity index 98%
rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/StringChunkProcessor.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/StringChunkProcessor.java
index afdaf1e..9ea1c64 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/StringChunkProcessor.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/StringChunkProcessor.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.chunk;
+package space.qu4nt.entanglementlib.core.util.chunk;
import java.util.Objects;
import java.util.stream.IntStream;
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/collection/SafeList.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/collection/SafeList.java
similarity index 85%
rename from src/main/java/space/qu4nt/entanglementlib/util/collection/SafeList.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/collection/SafeList.java
index d7d7672..9698eaa 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/collection/SafeList.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/collection/SafeList.java
@@ -3,10 +3,9 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.collection;
+package space.qu4nt.entanglementlib.core.util.collection;
import org.jetbrains.annotations.NotNull;
-import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased;
import java.util.ArrayList;
import java.util.Iterator;
@@ -24,9 +23,6 @@
*/
public class SafeList implements Iterable {
- @SuppressWarnings({"rawtypes"})
- private static final LanguageInstanceBased lang = LanguageInstanceBased.create(SafeList.class);
-
private final ArrayList list = new ArrayList<>();
/**
@@ -56,7 +52,7 @@ public synchronized void add(int index, T item) {
public synchronized T remove(int index) {
if (index < 0 || index >= list.size())
- throw new IndexOutOfBoundsException(lang.argsNonTopKey("index-out-of-bounds-exc", index, list.size()));
+ throw new IndexOutOfBoundsException("index-out-of-bounds-exc");
return list.remove(index);
}
@@ -66,13 +62,13 @@ public synchronized boolean remove(T item) {
public synchronized T get(int index) {
if (index < 0 || index >= list.size())
- throw new IndexOutOfBoundsException(lang.argsNonTopKey("index-out-of-bounds-exc", index, list.size()));
+ throw new IndexOutOfBoundsException("index-out-of-bounds-exc");
return list.get(index);
}
public synchronized T set(int index, T item) {
if (index < 0 || index >= list.size())
- throw new IndexOutOfBoundsException(lang.argsNonTopKey("index-out-of-bounds-exc", index, list.size()));
+ throw new IndexOutOfBoundsException("index-out-of-bounds-exc");
return list.set(index, item);
}
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Arrays.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Arrays.java
similarity index 98%
rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Arrays.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Arrays.java
index 8589f12..f7ff7f3 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Arrays.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Arrays.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.wrapper;
+package space.qu4nt.entanglementlib.core.util.wrapper;
import java.math.BigInteger;
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Hex.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Hex.java
similarity index 84%
rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Hex.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Hex.java
index fd1dde6..c106848 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Hex.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Hex.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.wrapper;
+package space.qu4nt.entanglementlib.core.util.wrapper;
/**
* {@code BouncyCastle} 라이브러리의 {@code Hex} 클래스의 몇 가지
@@ -22,7 +22,7 @@ public final class Hex {
* @return 바이트 배열의 Hex 문자열 표현
*/
public static String toHexString(byte[] bytes) {
- return org.bouncycastle.util.encoders.Hex.toHexString(bytes);
+ return null; // Q. T. Felix TODO: native
}
}
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Pair.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Pair.java
similarity index 91%
rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Pair.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Pair.java
index cbd8c89..9286511 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Pair.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Pair.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.wrapper;
+package space.qu4nt.entanglementlib.core.util.wrapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Tuple.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Tuple.java
similarity index 92%
rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Tuple.java
rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Tuple.java
index 7bb71a0..e1288b3 100644
--- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Tuple.java
+++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Tuple.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib.util.wrapper;
+package space.qu4nt.entanglementlib.core.util.wrapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/security/build.gradle.kts b/security/build.gradle.kts
new file mode 100644
index 0000000..2208733
--- /dev/null
+++ b/security/build.gradle.kts
@@ -0,0 +1,73 @@
+plugins {
+ id("me.champeau.jmh") version "0.7.3"
+}
+
+dependencies {
+ implementation(project(":core"))
+ implementation(project(":annotations"))
+
+ // JMH
+ jmh("org.openjdk.jmh:jmh-core:1.37")
+ jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37")
+ // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core
+ testImplementation("org.openjdk.jmh:jmh-core:1.37")
+ // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess
+ testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
+ jmhAnnotationProcessor("org.projectlombok:lombok:1.18.42")
+}
+
+sourceSets {
+ named("jmh") {
+ java {
+ srcDirs("src/benchmark/java")
+ }
+ resources {
+ srcDirs("src/benchmark/resources")
+ }
+ }
+}
+
+tasks.jar {
+ from("../entlib-native/dist") {
+ into("native")
+ include(
+ "linux/libentlib_native_aarch64.so",
+ "linux/libentlib_native_x86_64.so",
+ "macos/libentlib_native_aarch64.dylib",
+ "macos/libentlib_native_universal.dylib",
+ "macos/libentlib_native_x86_64.dylib",
+ "windows/entlib_native_x86_64.dll"
+ )
+ }
+}
+
+//
+// JMH - start
+//
+jmh {
+ jmhVersion.set("1.37")
+
+ // 테스트 벤치마킹 클래스 등록
+ includeTests.set(true)
+ includes.set(listOf(".*_JMHBenchmark"))
+
+ // 벤치마크 실행 시 필요한 jvm 인자 중앙 제어
+ jvmArgs.set(listOf(
+ "--enable-native-access=ALL-UNNAMED",
+ "--enable-preview",
+ "-Djava.library.path=${rootDir}/native-benchmark/target/debug",
+ "-Xms2g", "-Xmx2g" // gc 간섭 최소화
+ ))
+ fork.set(1)
+
+ // 결과 출력 포맷
+ resultFormat.set("JSON")
+
+ // 벤치마크 수행 옵션 (프로세스 및 반복 횟수)
+ fork.set(1)
+ warmupIterations.set(3)
+ iterations.set(5)
+}
+//
+// JMH - end
+//
\ No newline at end of file
diff --git a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java
similarity index 98%
rename from src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java
rename to security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java
index 6a0bab6..13d93a0 100644
--- a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java
+++ b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java
@@ -1,7 +1,8 @@
-package space.qu4nt.entanglementlib.benchmark;/*
+/*
* Copyright © 2025-2026 Quant.
* Under License "PolyForm Noncommercial License 1.0.0".
*/
+package space.qu4nt.entanglementlib.benchmark;
import org.openjdk.jmh.annotations.*;
diff --git a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java
similarity index 99%
rename from src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java
rename to security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java
index 82f5979..8e83dd0 100644
--- a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java
+++ b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java
@@ -1,7 +1,8 @@
-package space.qu4nt.entanglementlib.benchmark;/*
+/*
* Copyright © 2025-2026 Quant.
* Under License "PolyForm Noncommercial License 1.0.0".
*/
+package space.qu4nt.entanglementlib.benchmark;
import org.openjdk.jmh.annotations.*;
@@ -59,7 +60,6 @@ public class NativeCall_JMHBenchmark {
);
-
// 보안 벡터 처리 함수
FFM_SECURE_VECTOR_HANDLE = LINKER.downcallHandle(
lookup.find("process_secure_vector").orElseThrow(),
@@ -74,7 +74,6 @@ public class NativeCall_JMHBenchmark {
);
-
// 다항식 모듈러 가산 함수
FFM_POLY_MODULAR_ADD = LINKER.downcallHandle(
lookup.find("poly_modular_add").orElseThrow(),
diff --git a/src/benchmark/resources/logback.xml b/security/src/benchmark/resources/logback.xml
similarity index 100%
rename from src/benchmark/resources/logback.xml
rename to security/src/benchmark/resources/logback.xml
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityConfig.java b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityConfig.java
new file mode 100644
index 0000000..1b9db53
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityConfig.java
@@ -0,0 +1,32 @@
+package space.qu4nt.entanglementlib.security;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetbrains.annotations.Nullable;
+import space.qu4nt.entanglementlib.core.util.Nill;
+import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory;
+import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext;
+
+@Getter
+@Setter
+public class EntanglementLibSecurityConfig {
+
+ private NativeSpecContext nativeContext;
+ private HeuristicArenaFactory.ArenaMode arenaMode;
+
+ private EntanglementLibSecurityConfig(final NativeSpecContext nativeContext,
+ final @Nullable HeuristicArenaFactory.ArenaMode arenaMode) {
+ this.nativeContext = nativeContext;
+ this.arenaMode = arenaMode;
+ }
+
+ public static EntanglementLibSecurityConfig create(
+ final NativeSpecContext nativeContext,
+ final @Nullable HeuristicArenaFactory.ArenaMode arenaMode
+ ) {
+ return new EntanglementLibSecurityConfig(
+ Nill.nullDef(nativeContext, NativeSpecContext::defaults),
+ Nill.nullDef(arenaMode, () -> HeuristicArenaFactory.ArenaMode.AUTO)
+ );
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java
new file mode 100644
index 0000000..66eb4ab
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java
@@ -0,0 +1,17 @@
+package space.qu4nt.entanglementlib.security;
+
+import org.jetbrains.annotations.NotNull;
+import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory;
+import space.qu4nt.entanglementlib.security.entlibnative.NativeLoader;
+
+public final class EntanglementLibSecurityFacade {
+
+ private EntanglementLibSecurityFacade() {
+ throw new UnsupportedOperationException("cannot access");
+ }
+
+ public static void initialize(@NotNull EntanglementLibSecurityConfig config) {
+ NativeLoader.loadNativeLibrary(config);
+ HeuristicArenaFactory.setGlobalArenaMode(config.getArenaMode());
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java
new file mode 100644
index 0000000..4882862
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java
@@ -0,0 +1,109 @@
+package space.qu4nt.entanglementlib.security.crypto;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+
+import java.lang.foreign.MemorySegment;
+
+@Slf4j
+public final class ChaCha20 {
+
+ private ChaCha20() {
+ throw new UnsupportedOperationException("cannot access");
+ }
+
+ /// ChaCha20-Poly1305 암호화를 수행합니다.
+ ///
+ /// @param context 메모리 소거 생명주기를 관리하는 컨텍스트
+ /// @param key 32바이트 대칭키 (호출 즉시 네이티브로 이전 후 소거됨)
+ /// @param nonce 12바이트 Nonce (호출 즉시 네이티브로 이전 후 소거됨)
+ /// @param aad 추가 인증 데이터 (선택 사항)
+ /// @param plaintext 암호화할 평문 데이터 (호출 즉시 네이티브로 이전 후 소거됨)
+ /// @return 암호화된 사이퍼텍스트(MAC 태그 포함)를 담은 SDC 객체
+ public static @NotNull SensitiveDataContainer encrypt(
+ final @NotNull SDCScopeContext context,
+ final @NotNull SensitiveDataContainer key,
+ final @NotNull SensitiveDataContainer nonce,
+ final @Nullable SensitiveDataContainer aad,
+ final @NotNull SensitiveDataContainer plaintext) throws ELIBSecurityProcessException {
+
+ try {
+ // 입력 데이터를 SDC에 할당하여 네이티브 메모리로 이전 (forceWipe = true로 힙 메모리 즉시 소거)
+ MemorySegment aadSegment = MemorySegment.NULL;
+ long aadLen = 0;
+ if (aad != null) {
+ aadSegment = InternalNativeBridge.unwrapMemorySegment(aad);
+ aadLen = InternalNativeBridge.unwrapMemorySegment(aad).byteSize();
+ }
+
+ // Rust FFI 호출 (Callee-allocated Opaque Pointer 반환)
+ MemorySegment rustBufferPtr = (MemorySegment) EntLibNativeManager
+ .call(Function.ChaCha20_Poly1305_Encrypt)
+ .invokeExact(
+ InternalNativeBridge.unwrapMemorySegment(key), (long) InternalNativeBridge.unwrapMemorySegment(key).byteSize(),
+ InternalNativeBridge.unwrapMemorySegment(nonce), (long) InternalNativeBridge.unwrapMemorySegment(nonce).byteSize(),
+ aadSegment, aadLen,
+ InternalNativeBridge.unwrapMemorySegment(plaintext), (long) InternalNativeBridge.unwrapMemorySegment(plaintext).byteSize()
+ );
+
+ if (rustBufferPtr.equals(MemorySegment.NULL)) {
+ throw new ELIBSecurityProcessException("ChaCha20 암호화 실패: 유효하지 않은 입력 길이");
+ }
+
+ return EntLibNativeManager.transferNativeBufferBindToContext(
+ context, rustBufferPtr
+ );
+ } catch (Throwable t) {
+ log.error("ChaCha20 암호화 중 치명적 보안 예외 발생", t);
+ throw new ELIBSecurityProcessException("암호화 프로세스 실패", t);
+ }
+ }
+
+ /// ChaCha20-Poly1305 복호화를 수행합니다.
+ ///
+ /// @return 복호화된 평문 데이터를 담은 SDC 객체
+ public static @NotNull SensitiveDataContainer decrypt(
+ final @NotNull SDCScopeContext context,
+ final @NotNull SensitiveDataContainer key,
+ final @NotNull SensitiveDataContainer nonce,
+ final @Nullable SensitiveDataContainer aad,
+ final @NotNull SensitiveDataContainer ciphertext) throws ELIBSecurityProcessException {
+
+ try {
+ MemorySegment aadSegment = MemorySegment.NULL;
+ long aadLen = 0;
+ if (aad != null) {
+ aadSegment = InternalNativeBridge.unwrapMemorySegment(aad);
+ aadLen = InternalNativeBridge.unwrapMemorySegment(aad).byteSize();
+ }
+
+ MemorySegment rustBufferPtr = (MemorySegment) EntLibNativeManager
+ .call(Function.ChaCha20_Poly1305_Decrypt)
+ .invokeExact(
+ InternalNativeBridge.unwrapMemorySegment(key), (long) InternalNativeBridge.unwrapMemorySegment(key).byteSize(),
+ InternalNativeBridge.unwrapMemorySegment(nonce), (long) InternalNativeBridge.unwrapMemorySegment(nonce).byteSize(),
+ aadSegment, aadLen,
+ InternalNativeBridge.unwrapMemorySegment(ciphertext), (long) InternalNativeBridge.unwrapMemorySegment(ciphertext).byteSize()
+ );
+
+ // Authentication Failed 또는 입력 오류
+ if (rustBufferPtr.equals(MemorySegment.NULL)) {
+ throw new ELIBSecurityProcessException("ChaCha20 복호화 실패: 무결성 검증(MAC) 실패 또는 유효하지 않은 입력");
+ }
+
+ return EntLibNativeManager.transferNativeBufferBindToContext(
+ context, rustBufferPtr
+ );
+ } catch (Throwable t) {
+ log.error("ChaCha20 복호화 중 치명적 보안 예외 발생", t);
+ throw new ELIBSecurityProcessException("복호화 프로세스 실패", t);
+ }
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java
new file mode 100644
index 0000000..72d0eb7
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java
@@ -0,0 +1,107 @@
+package space.qu4nt.entanglementlib.security.crypto.encode;
+
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+
+import java.lang.foreign.MemorySegment;
+
+/**
+ * 네이티브 메모리(native memory) 기반의 안전한 Base64 인코딩 및 디코딩 유틸리티입니다.
+ * 모든 입출력은 {@link SensitiveDataContainer}를 통해 소유권(ownership)이 통제되며,
+ * 가비지 컬렉터(garbage collector)가 관리하는 자바 힙(java heap) 메모리에 민감 데이터를 노출하지 않습니다.
+ */
+public final class Base64 {
+
+ private Base64() {
+ throw new AssertionError("유틸리티 클래스는 인스턴스화할 수 없습니다.");
+ }
+
+ /**
+ * 제공된 보안 컨테이너의 데이터를 Base64로 인코딩합니다.
+ * 반환된 새 컨테이너는 호출자(caller)가 소유권을 가지며, 세션 종료 시 명시적으로 소거해야 합니다.
+ *
+ * @param input 원본 데이터가 담긴 보안 컨테이너
+ * @return Base64 인코딩 결과가 담긴 새로운 보안 컨테이너
+ */
+ public static SensitiveDataContainer encode(final SensitiveDataContainer input) {
+ if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) {
+ throw new IllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다.");
+ }
+
+ final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize();
+ final int required = (int) (((inputLen + 2) / 3) * 4);
+
+ SensitiveDataContainer output = new SensitiveDataContainer(required);
+
+ try {
+ long result = (long) EntLibNativeManager.call(Function.Base64_encode).invokeExact(
+ InternalNativeBridge.unwrapMemorySegment(input),
+ inputLen,
+ InternalNativeBridge.unwrapMemorySegment(output),
+ (long) required
+ );
+
+ if (result < 0) {
+ output.close();
+ throw new RuntimeException("인코딩 중 네이티브 오류 발생 (error code): " + result);
+ }
+
+ return output;
+ } catch (Throwable t) {
+ output.close();
+ throw new RuntimeException("FFM API 인코딩 호출 실패", t);
+ }
+ }
+
+ /**
+ * 제공된 보안 컨테이너의 Base64 데이터를 디코딩합니다.
+ * 반환된 새 컨테이너는 호출자(caller)가 소유권을 가지며, 세션 종료 시 명시적으로 소거해야 합니다.
+ *
+ * @param input Base64 인코딩 데이터가 담긴 보안 컨테이너
+ * @return 디코딩된 원본 데이터가 담긴 새로운 보안 컨테이너
+ */
+ public static SensitiveDataContainer decode(final SensitiveDataContainer input) {
+ if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) {
+ throw new IllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다.");
+ }
+
+ final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize();
+ final int maxRequired = (int) ((inputLen / 4 + 1) * 3);
+
+ // 임시 컨테이너 생성 (try-with-resources로 자동 소거 보장)
+ try (SensitiveDataContainer tempOutput = new SensitiveDataContainer(maxRequired)) {
+
+ long result = (long) EntLibNativeManager.call(Function.Base64_decode).invokeExact(
+ InternalNativeBridge.unwrapMemorySegment(input),
+ inputLen,
+ InternalNativeBridge.unwrapMemorySegment(tempOutput),
+ (long) maxRequired
+ );
+
+ if (result < 0) {
+ throw new RuntimeException("디코딩 중 네이티브 오류 발생 (error code): " + result);
+ }
+
+ // 실제 크기에 맞는 반환용 컨테이너 생성 (소유권 이전용)
+ // result(실제 길이)만큼만 할당
+ SensitiveDataContainer exactOutput = new SensitiveDataContainer((int) result);
+
+ try {
+ // 데이터 복사
+ MemorySegment src = InternalNativeBridge.unwrapMemorySegment(tempOutput);
+ MemorySegment dst = InternalNativeBridge.unwrapMemorySegment(exactOutput);
+ MemorySegment.copy(src, 0, dst, 0, result);
+ return exactOutput;
+ } catch (Exception e) {
+ exactOutput.close(); // 복사 중 예외 시 반환용 컨테이너도 닫음
+ throw e;
+ }
+ // 메소드 종료 시 tempOutput.close()가 자동 호출
+
+ } catch (Throwable t) {
+ throw new RuntimeException("FFM API 디코딩 호출 실패", t);
+ }
+ }
+}
\ No newline at end of file
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java
new file mode 100644
index 0000000..46f024a
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java
@@ -0,0 +1,218 @@
+package space.qu4nt.entanglementlib.security.crypto.hash;
+
+import org.jetbrains.annotations.NotNull;
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.invoke.MethodHandle;
+import java.util.stream.IntStream;
+
+public final class Hash {
+
+ public static SensitiveDataContainer sha2(
+ final int length,
+ @NotNull SDCScopeContext scope,
+ @NotNull SensitiveDataContainer input
+ ) throws Throwable {
+ final int[] ableLens = {224, 256, 384, 512};
+ if (IntStream.of(ableLens).noneMatch(l -> l == length))
+ return null;
+
+ // 길이에 맞는 함수 등록
+ Function newContextFunc, updateFunc, finalizeFunc, freeFunc;
+ switch (length) {
+ case 224 -> {
+ newContextFunc = Function.SHA2_224_New;
+ updateFunc = Function.SHA2_224_Update;
+ finalizeFunc = Function.SHA2_224_Finalize;
+ freeFunc = Function.SHA2_224_Free;
+ }
+ case 256 -> {
+ newContextFunc = Function.SHA2_256_New;
+ updateFunc = Function.SHA2_256_Update;
+ finalizeFunc = Function.SHA2_256_Finalize;
+ freeFunc = Function.SHA2_256_Free;
+ }
+ case 384 -> {
+ newContextFunc = Function.SHA2_384_New;
+ updateFunc = Function.SHA2_384_Update;
+ finalizeFunc = Function.SHA2_384_Finalize;
+ freeFunc = Function.SHA2_384_Free;
+ }
+ case 512 -> {
+ newContextFunc = Function.SHA2_512_New;
+ updateFunc = Function.SHA2_512_Update;
+ finalizeFunc = Function.SHA2_512_Finalize;
+ freeFunc = Function.SHA2_512_Free;
+ }
+ default -> throw new ELIBSecurityProcessException("불가능한 길이");
+ }
+
+ // 컨텍스트 생성
+ MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact();
+ boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그
+
+ try {
+ // 데이터 업데이트
+ MethodHandle updateMH = EntLibNativeManager.call(updateFunc);
+ MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input);
+ int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize());
+ if (status != 0) {
+ throw new ELIBSecurityProcessException("업데이트 실패, 상태 코드: " + status);
+ }
+
+ // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨)
+ MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc)
+ .invokeExact(ctx);
+ isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환
+
+ if (secureBufferPtr.equals(MemorySegment.NULL))
+ throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다!");
+
+ return EntLibNativeManager.transferNativeBufferBindToContext(
+ scope, secureBufferPtr
+ ); // 이 작업 내에서 버퍼 소거가 진행됨
+ } finally {
+ // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free
+ if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) {
+ EntLibNativeManager.call(freeFunc).invokeExact(ctx);
+ }
+ }
+ }
+
+ public static SensitiveDataContainer sha3(
+ final int length,
+ @NotNull SDCScopeContext scope,
+ @NotNull SensitiveDataContainer input
+ ) throws Throwable {
+ final int[] ableLens = {224, 256, 384, 512};
+ if (IntStream.of(ableLens).noneMatch(l -> l == length))
+ return null;
+
+ // 길이에 맞는 함수 등록
+ Function newContextFunc, updateFunc, finalizeFunc, freeFunc;
+ switch (length) {
+ case 224 -> {
+ newContextFunc = Function.SHA3_224_New;
+ updateFunc = Function.SHA3_224_Update;
+ finalizeFunc = Function.SHA3_224_Finalize;
+ freeFunc = Function.SHA3_224_Free;
+ }
+ case 256 -> {
+ newContextFunc = Function.SHA3_256_New;
+ updateFunc = Function.SHA3_256_Update;
+ finalizeFunc = Function.SHA3_256_Finalize;
+ freeFunc = Function.SHA3_256_Free;
+ }
+ case 384 -> {
+ newContextFunc = Function.SHA3_384_New;
+ updateFunc = Function.SHA3_384_Update;
+ finalizeFunc = Function.SHA3_384_Finalize;
+ freeFunc = Function.SHA3_384_Free;
+ }
+ case 512 -> {
+ newContextFunc = Function.SHA3_512_New;
+ updateFunc = Function.SHA3_512_Update;
+ finalizeFunc = Function.SHA3_512_Finalize;
+ freeFunc = Function.SHA3_512_Free;
+ }
+ default -> throw new ELIBSecurityProcessException("불가능한 길이");
+ }
+
+ // 컨텍스트 생성
+ MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact();
+ boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그
+
+ try {
+ // 데이터 업데이트
+ MethodHandle updateMH = EntLibNativeManager.call(updateFunc);
+ MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input);
+ int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize());
+ if (status != 0) {
+ throw new ELIBSecurityProcessException("업데이트 실패, 상태 코드: " + status);
+ }
+
+ // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨)
+ MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc)
+ .invokeExact(ctx);
+ isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환
+
+ if (secureBufferPtr.equals(MemorySegment.NULL))
+ throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다!");
+
+ return EntLibNativeManager.transferNativeBufferBindToContext(
+ scope, secureBufferPtr
+ ); // 이 작업 내에서 버퍼 소거가 진행됨
+ } finally {
+ // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free
+ if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) {
+ EntLibNativeManager.call(freeFunc).invokeExact(ctx);
+ }
+ }
+ }
+
+ public static SensitiveDataContainer sha3Shake(
+ final int length,
+ final long byteOutLen,
+ @NotNull SDCScopeContext scope,
+ @NotNull SensitiveDataContainer input
+ ) throws Throwable {
+ final int[] ableLens = {128, 256};
+ if (IntStream.of(ableLens).noneMatch(l -> l == length))
+ return null;
+
+ // 길이에 맞는 함수 등록
+ Function newContextFunc, updateFunc, finalizeFunc, freeFunc;
+ switch (length) {
+ case 128 -> {
+ newContextFunc = Function.SHA3_SHAKE128_New;
+ updateFunc = Function.SHA3_SHAKE128_Update;
+ finalizeFunc = Function.SHA3_SHAKE128_Finalize;
+ freeFunc = Function.SHA3_SHAKE128_Free;
+ }
+ case 256 -> {
+ newContextFunc = Function.SHA3_SHAKE256_New;
+ updateFunc = Function.SHA3_SHAKE256_Update;
+ finalizeFunc = Function.SHA3_SHAKE256_Finalize;
+ freeFunc = Function.SHA3_SHAKE256_Free;
+ }
+ default -> throw new ELIBSecurityProcessException("불가능한 길이");
+ }
+
+ // 컨텍스트 생성
+ MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact();
+ boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그
+
+ try {
+ // 데이터 업데이트
+ MethodHandle updateMH = EntLibNativeManager.call(updateFunc);
+ MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input);
+ int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize());
+ if (status != 0) {
+ throw new ELIBSecurityProcessException("업데이트 실패, 상태 코드: " + status);
+ }
+
+ // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨)
+ MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc)
+ .invokeExact(ctx, byteOutLen);
+ isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환
+
+ if (secureBufferPtr.equals(MemorySegment.NULL))
+ throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다.");
+
+ return EntLibNativeManager.transferNativeBufferBindToContext(
+ scope, secureBufferPtr
+ ); // 이 작업 내에서 버퍼 소거가 진행됨
+ } finally {
+ // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free
+ if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) {
+ EntLibNativeManager.call(freeFunc).invokeExact(ctx);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java
new file mode 100644
index 0000000..e5e9473
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java
@@ -0,0 +1,90 @@
+package space.qu4nt.entanglementlib.security.crypto.rng;
+
+import org.jetbrains.annotations.NotNull;
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+
+/// 하드웨어 진난수 및 양자 난수(quantum random number) 생성 인터페이스입니다.
+/// 모든 반환 데이터는 네이티브 스코프 내에서 완벽한 데이터 소거(zeroize)가 보장됩니다.
+///
+/// @author Q. T. Felix
+public final class RNG {
+
+ public static final byte LOCAL_HARDWARE = (byte) 0;
+ public static final byte QUANTUM_NETWORK = (byte) 1;
+
+ /// 지정된 엔트로피 전략(entropy strategy)에 따라 난수를 생성하고 컨테이너에 바인딩합니다.
+ ///
+ /// @param entropyStrategy 난수 생성 전략 (로컬 하드웨어 또는 양자 네트워크)
+ /// @param scope 보안 데이터 생명주기를 관리하는 컨텍스트(context)
+ /// @param length 생성할 난수의 바이트 길이
+ /// @return `heap` 오염 없이 난수 데이터를 소유하는 민감 데이터 컨테이너
+ /// @throws ELIBSecurityProcessException 난수 생성 또는 복사 중 에러 발생 시
+ public static SensitiveDataContainer generateRNG(final byte entropyStrategy,
+ final @NotNull SDCScopeContext scope,
+ final long length) throws ELIBSecurityProcessException {
+
+ // FFI 호출에 필요한 파라미터 및 포인터를 임시 관리하기 위한 로컬 아레나(arena)
+ try (Arena localArena = Arena.ofConfined()) {
+ MemorySegment errFlag = localArena.allocate(ValueLayout.JAVA_BYTE);
+
+ // 혼합 난수 생성기(mixed rng) 인스턴스 초기화
+ MemorySegment rngPtr = (MemorySegment) EntLibNativeManager.call(Function.RNG_MIXED_New_With_Strategy)
+ .invokeExact(entropyStrategy, errFlag);
+ checkError(errFlag.get(ValueLayout.JAVA_BYTE, 0));
+
+ MemorySegment secureBufPtr = null;
+ try {
+ // 난수 버퍼 생성
+ secureBufPtr = (MemorySegment) EntLibNativeManager.call(Function.RNG_MIXED_Generate)
+ .invokeExact(rngPtr, length, errFlag);
+ checkError(errFlag.get(ValueLayout.JAVA_BYTE, 0));
+
+ // 러스트 영역의 보안 버퍼(secure buffer)에서 실제 데이터 포인터 추출
+ MemorySegment dataPtr = (MemorySegment) EntLibNativeManager.call(Function.Callee_Secure_Buffer_Data)
+ .invokeExact(secureBufPtr);
+ MemorySegment nativeDataSegment = dataPtr.reinterpret(length);
+
+ // 컨텍스트를 통한 SDC 할당
+ SensitiveDataContainer sdc = scope.allocate((int) length);
+
+ // heap 배열을 거치지 않고 네이티브 대 네이티브로 직접 메모리 복사 수행
+ MemorySegment.copy(nativeDataSegment, 0, InternalNativeBridge.unwrapMemorySegment(sdc), 0, length);
+
+ return sdc;
+ } finally {
+ // 러스트 힙에 할당된 포인터의 강제 해제 및 소거 유도
+ if (secureBufPtr != null && !secureBufPtr.equals(MemorySegment.NULL)) {
+ EntLibNativeManager.call(Function.Callee_Secure_Buffer_Free).invokeExact(secureBufPtr);
+ }
+ if (rngPtr != null && !rngPtr.equals(MemorySegment.NULL)) {
+ EntLibNativeManager.call(Function.RNG_MIXED_Free).invokeExact(rngPtr);
+ }
+ }
+ } catch (Throwable e) {
+ throw new ELIBSecurityProcessException("네이티브 브릿지를 통한 난수 생성에 실패했습니다.", e);
+ }
+ }
+
+ private static void checkError(byte errorCode) {
+ if (errorCode == 0) return;
+ throw switch (errorCode) {
+ case 1 -> new ELIBSecurityNativeCritical("지원하지 않는 하드웨어(hardware) 환경입니다!");
+ case 2 -> new ELIBSecurityNativeCritical("엔트로피(entropy)가 고갈되었습니다!");
+ case 3 -> new ELIBSecurityNativeCritical("잘못된 메모리 포인터(pointer) 참조입니다!");
+ case 4 -> new ELIBSecurityNativeCritical("양자 난수 생성기 네트워크(network) 통신에 실패했습니다!");
+ case 5 -> new ELIBSecurityNativeCritical("양자 난수 데이터 파싱(parsing) 에러가 발생했습니다!");
+ case 6 -> new ELIBSecurityNativeCritical("잘못된 파라미터(parameter)가 전달되었습니다!");
+ default -> new ELIBSecurityNativeCritical("알 수 없는 네이티브(native) 에러 코드: " + errorCode);
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/qu4nt/entanglementlib/HeuristicArenaFactory.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/HeuristicArenaFactory.java
similarity index 98%
rename from src/main/java/space/qu4nt/entanglementlib/HeuristicArenaFactory.java
rename to security/src/main/java/space/qu4nt/entanglementlib/security/data/HeuristicArenaFactory.java
index 1dc2662..37bd509 100644
--- a/src/main/java/space/qu4nt/entanglementlib/HeuristicArenaFactory.java
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/HeuristicArenaFactory.java
@@ -3,7 +3,7 @@
* Under License "PolyForm Noncommercial License 1.0.0".
*/
-package space.qu4nt.entanglementlib;
+package space.qu4nt.entanglementlib.security.data;
import lombok.extern.slf4j.Slf4j;
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/InternalNativeBridge.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/InternalNativeBridge.java
new file mode 100644
index 0000000..0285cd4
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/InternalNativeBridge.java
@@ -0,0 +1,24 @@
+package space.qu4nt.entanglementlib.security.data;
+
+import org.jetbrains.annotations.NotNull;
+import space.qu4nt.entanglementlib.annotations.Unsafe;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.util.Objects;
+
+public final class InternalNativeBridge {
+
+ private InternalNativeBridge() {
+ }
+
+ @Unsafe("사용자가 직접 Arena를 조작하는 것은 권장되지 않음")
+ public static @NotNull Arena unwrapArena(final @NotNull SensitiveDataContainer container) {
+ return Objects.requireNonNull(container, "container").getArena();
+ }
+
+ @Unsafe("MemorySegment는 내부적으로 heap에 데이터가 운반될 수 있는 기능을 포함")
+ public static @NotNull MemorySegment unwrapMemorySegment(final @NotNull SensitiveDataContainer container) {
+ return Objects.requireNonNull(container, "container").getMemorySegment();
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCConsumer.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCConsumer.java
new file mode 100644
index 0000000..27b03ee
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCConsumer.java
@@ -0,0 +1,9 @@
+package space.qu4nt.entanglementlib.security.data;
+
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+
+@FunctionalInterface
+public interface SDCConsumer {
+
+ void accept(SensitiveDataContainer container) throws ELIBSecurityProcessException;
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCFunction.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCFunction.java
new file mode 100644
index 0000000..3149e0a
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCFunction.java
@@ -0,0 +1,9 @@
+package space.qu4nt.entanglementlib.security.data;
+
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+
+@FunctionalInterface
+public interface SDCFunction {
+
+ R apply(SensitiveDataContainer container) throws ELIBSecurityProcessException;
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java
new file mode 100644
index 0000000..c08aa8d
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java
@@ -0,0 +1,61 @@
+package space.qu4nt.entanglementlib.security.data;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/// 보안 작업 흐름 내에서 생성되는 모든 민감 데이터 컨테이너를 추적하고,
+/// 스코프 종료 시 일괄 소거(zeroize)를 보장하는 컨텍스트 클래스입니다.
+///
+/// @author Q. T. Felix
+/// @since 1.1.1
+@Slf4j
+public final class SDCScopeContext implements AutoCloseable {
+
+ private final List<@NotNull SensitiveDataContainer> trackedContainers = Collections.synchronizedList(new ArrayList<>());
+ private volatile boolean isAlive = true;
+
+ /// 스코프 내에서 새로운 [SensitiveDataContainer]를 할당하는 메소드입니다.
+ /// 생성된 컨테이너는 자동으로 현재 스코프에 바인딩됩니다.
+ public SensitiveDataContainer allocate(int size) {
+ checkAlive();
+ SensitiveDataContainer container = new SensitiveDataContainer(size);
+ trackedContainers.add(container);
+ return container;
+ }
+
+ /// 기존 바이트 배열로부터 데이터 소유권을 이전받는 컨테이너를 생성하는 메소드입니다.
+ public SensitiveDataContainer allocate(byte[] from, boolean forceWipe) {
+ checkAlive();
+ SensitiveDataContainer container = new SensitiveDataContainer(from, forceWipe);
+ trackedContainers.add(container);
+ return container;
+ }
+
+ private void checkAlive() {
+ if (!isAlive)
+ throw new IllegalStateException("이미 소거 완료된 보안 스코프입니다!");
+ }
+
+ @Override
+ public void close() {
+ if (!isAlive) return;
+ isAlive = false;
+
+ // 스냅샷 소거
+ synchronized (trackedContainers) {
+ for (int i = trackedContainers.size() - 1; i >= 0; i--) {
+ SensitiveDataContainer sdc = trackedContainers.get(i);
+ try {
+ sdc.close();
+ } catch (Exception e) {
+ log.error("컨텍스트 내부 컨테이너 소거 중 오류 발생", e);
+ }
+ }
+ trackedContainers.clear();
+ }
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java
new file mode 100644
index 0000000..459f5f7
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright © 2025-2026 Quant.
+ * Under License "PolyForm Noncommercial License 1.0.0".
+ */
+
+package space.qu4nt.entanglementlib.security.data;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+import space.qu4nt.entanglementlib.annotations.CallerResponsibility;
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+
+import java.io.IOException;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.util.Arrays;
+import java.util.Objects;
+
+/// `Rust`의 소유권(ownership) 개념처럼 이 클래스도 전달받은 민감 정보에 대한 소유권을 가집니다.
+/// 이 클래스에 저장된 데이터는 진행중인 세션에 종속되고, 세션이 종료됨에 따라 모든 데이터가 완벽하게
+/// 소거됩니다. 데이터 소거는 `entlib-native`에서 진행됩니다.
+///
+/// 이 객체를 통해 네이티브 함수에 필요한 데이터를 전달하거나, 함수 실행 결과를 받을 수 있습니다.
+/// 이 경우 **"호출자 패턴"이 사용되며, 해당 데이터의 소거는 보장되지만 할당 해제 권한이 이 객체에
+/// 부여**되어 반드시 지정된 데이터에 대한 할당 해제 함수를 호출해야 합니다.
+///
+/// 하지만 이 객체는 [AutoCloseable] 인터페이스를 구현함으로서 [#close()] 수행 시 호출자 측
+/// 메모리 해제 함수를 자동으로 호출해줍니다.
+///
+/// ---
+///
+/// [#SensitiveDataContainer(byte\[\], boolean)] 생성자를 사용하여 전달받은 바이트 배열
+/// 데이터에 대한 `heap` 소거 진행 여부를 지정할 수 있습니다. 논리 값이 `true`일 경우 데이터는
+/// Java `heap` 메모리에서 즉시 소거됩니다.
+///
+/// # Usage
+/// 이전엔 일일이 컨테이너를 연결해주어야 했습니다. 이 과정을 축약하기 위해 [SDCScopeContext]
+/// 개념을 도입했습니다. 이 객체를 통해 스코프를 생성하고 내부의 전체 컨테이너에 대해 작업
+/// 완료 시 안전하게 소거 및 할당 해제 작업을 자동으로 수행하도록 할 수 있습니다.
+/// ```java
+/// // 보안 세션 시작
+/// try (SecureScopeContext scope = new SecureScopeContext()) {
+///
+/// // allocate raw key data
+/// SensitiveDataContainer rawKey = scope.allocate(32);
+///
+/// // generate PQC key pair
+/// SensitiveDataContainer pqcCiphertext = PQC.encapsulate(scope, rawKey);
+///
+/// // encode to Base64 for transmission
+/// SensitiveDataContainer b64Data = Base64.encode(scope, pqcCiphertext);
+///
+/// // ...network transmission 또는 별도의 로직...
+///
+/// } // 스코프가 닫히는 순간 rawKey, pqcCiphertext, b64Data가 역순으로 완벽하게 소거
+/// // 네이티브에 FFI로 넘어온 할당 해제 함수를 자동으로 호출하여 지정된 객체에 대해 모두 수행
+/// ```
+/// 위와 같은 사용은 통신 체계에서 매우 유용하게 사용될 수 있습니다.
+///
+/// 작업량이 스코프 작업에 어울리지 않을 수도 있습니다. 예를 들어, 보안 난수 생성 및 세션 내에서
+/// 구조화된 경량 통신 체계를 구축할 때는 다음과 같이 사용될 수 있습니다.
+/// ```java
+/// SensitiveDataContainer.runScope(256, container -> {
+/// // ...Secure stuff...
+/// }); // 소비 패턴
+///
+/// int resultSize = SensitiveDataContainer.callScope(1024, container -> {
+/// // ...Secure stuff...
+/// return container.getMemorySegment().byteSize();
+/// }); // 반환 패턴
+/// ```
+/// 각 메소드의 모든 작업은 `try-with-resources` 블럭 내에서 사용됩니다.
+///
+/// # Safety
+/// 이 객체의 인스턴스를 직접 생성해야 하는 경우 다음과 같이 네이티브에 메모리 할당 해제 함수를
+/// 호출하여 사용해야 합니다.
+/// ```java
+/// SensitiveDataContainer sdc = ...;
+/// final MemorySegment ms = sdc.getMemorySegment();
+/// // ...Secure stuff...
+/// EntLibNativeManager.call(Function.Caller_Secure_Buffer_Wipe)
+/// .invokeExact(ms, ms.byteSize());
+/// ```
+/// 이 객체를 상속받아 사용되는 경우도 일관된 사용법을 따를 수 있습니다.
+///
+/// @author Q. T. Felix
+/// @see HeuristicArenaFactory Arena 자동 할당을 수행하는 클래스
+/// @see SDCScopeContext
+/// @since 1.1.0
+@Slf4j
+public class SensitiveDataContainer implements AutoCloseable {
+
+ @Getter(AccessLevel.PACKAGE)
+ private final Arena arena;
+ @Getter(AccessLevel.PACKAGE)
+ private final MemorySegment memorySegment;
+
+ /// 네이티브 메모리에 전달받은 정수 값(바이트 크기) 만큼의 메모리 세그먼트를
+ /// 생성하여 이 인스턴스를 생성합니다.
+ ///
+ /// # Safety
+ /// 이 생성자를 통해 인스턴스를 생성하면 호출자가 부담하는 보안 책임이 발생합니다.
+ /// 특별한 경우가 아닌 이상 이 방식을 통한 생성은 권장하지 않습니다.
+ ///
+ /// 또한, 이 생성자는 제거 예정은 없으나 권장되는 사용이 아닙니다. [SDCScopeContext]를
+ /// 통해 세션식 보안 작업을 수행하세요.
+ ///
+ /// @param allocateSIze 바이트 크기
+ /// @see #runScope(int, SDCConsumer) 스코프 작업 종료 시 자원 소거를 보장하는 정적 메소드
+ /// @see #callScope(int, SDCFunction) 스코프 작업 종료 후 자원을 소거하고 가공된 결과를 반환하는 정적 메소드
+ @CallerResponsibility("try-with-resource 사용 또는 close 메소드 직접 호출 필수")
+ public SensitiveDataContainer(final int allocateSIze) {
+ this.arena = HeuristicArenaFactory.intelligenceCreateArena();
+ this.memorySegment = Objects.requireNonNull(arena.allocate(allocateSIze));
+ }
+
+ /// 원본 바이트 배열을 전달받고 네이티브 메모리에 바인딩하여 이 인스턴스를 생성합니다.
+ ///
+ /// 생성 시점에 전달받은 원본 바이트 배열 데이터에 대한 소유권을 이 인스턴스에 넘길지 결정할 수
+ /// 있습니다. 논리 값이 `true`일 경우 데이터는 즉시 소거되고, 이는 이 인스턴스가 소유권을
+ /// 가질 필요가 없다는 것을 의미합니다.
+ ///
+ /// # Safety
+ /// 이 생성자를 통해 인스턴스를 생성하면 호출자가 부담하는 보안 책임이 발생합니다.
+ /// 특별한 경우가 아닌 이상 이 방식을 통한 생성은 권장하지 않습니다. 또한, 결국
+ /// `heap` 메모리에 데이터를 노출하는 것은 위험합니다.
+ ///
+ /// 또한, 이 생성자는 제거 예정은 없으나 권장되는 사용이 아닙니다. [SDCScopeContext]를
+ /// 통해 세션식 보안 작업을 수행하세요.
+ ///
+ /// @param from 네이티브 메모리에 바인딩할 원본 바이트 배열
+ /// @param forceWipe 인스턴스에 소유권 이전 여부
+ /// @see #runScope(int, SDCConsumer) 스코프 작업 종료 시 자원 소거를 보장하는 정적 메소드
+ /// @see #callScope(int, SDCFunction) 스코프 작업 종료 후 자원을 소거하고 가공된 결과를 반환하는 정적 메소드
+ @CallerResponsibility("try-with-resource 사용 또는 close 메소드 직접 호출 필수")
+ public SensitiveDataContainer(final byte @NotNull [] from, boolean forceWipe) {
+ this.arena = HeuristicArenaFactory.intelligenceCreateArena();
+ this.memorySegment = Objects.requireNonNull(arena.allocateFrom(ValueLayout.JAVA_BYTE, from));
+ if (forceWipe)
+ Arrays.fill(from, (byte) 0);
+ }
+
+ /**
+ * 보안 컨테이너의 생명주기를 자동으로 관리하는 실행 메소드입니다.
+ * Execute-Around-Pattern이 적용되어 작업 완료 즉시 메모리가 소거됨을 보장합니다.
+ *
+ * @param allocateSize 할당할 버퍼 크기
+ * @param action 컨테이너를 사용하여 수행할 보안 로직
+ */
+ public static void runScope(int allocateSize, SDCConsumer action) throws ELIBSecurityProcessException {
+ try (SensitiveDataContainer sdc = new SensitiveDataContainer(allocateSize)) {
+ action.accept(sdc);
+ }
+ }
+
+ /// 보안 컨테이너를 사용하여 값을 계산하고 반환하는 실행 메소드입니다.
+ /// 반환값은 민감한 데이터(`byte[]`)가 아닌, 가공된 결과물(암호화 성공 여부, 상태 코드 등)이어야 합니다.
+ ///
+ /// @param allocateSize 할당할 버퍼 크기
+ /// @param action 컨테이너를 사용하여 수행할 계산 로직
+ /// @return 계산 결과
+ public static R callScope(int allocateSize, SDCFunction action) throws ELIBSecurityProcessException {
+ try (SensitiveDataContainer sdc = new SensitiveDataContainer(allocateSize)) {
+ return action.apply(sdc);
+ }
+ }
+
+ public static void transmitZeroCopy(SensitiveDataContainer sdc, WritableByteChannel channel) throws ELIBSecurityProcessException {
+ if (!sdc.getArena().scope().isAlive()) {
+ throw new IllegalStateException("이미 소거 완료되었거나 유효하지 않은 컨테이너입니다!");
+ }
+
+ // 메소드 내부에서만 생존하는 임시 Direct ByteBuffer 뷰 생성
+ // 이 객체는 절대 외부로 반환되거나 다른 스레드로 전달되어서는 안 됨
+ ByteBuffer transientView = sdc.getMemorySegment().asByteBuffer();
+
+ // WritableByteChannel (SocketChannel 등)에 기록
+ // 채널이 Direct ByteBuffer를 인식하면 Java Heap으로 데이터를 복사하지 않고
+ // JNI를 통해 OS의 write() 또는 send() 시스템 콜로 메모리 주소를 직접 넘김
+ while (transientView.hasRemaining()) {
+ try {
+ channel.write(transientView);
+ } catch (IOException e) {
+ throw new ELIBSecurityProcessException(e);
+ }
+ }
+
+ // gc hint
+ transientView = null;
+ }
+
+ @Override
+ public void close() {
+ // 더 이상 하위 바인딩(bindings)을 관리하지 않아 로직 수정 //
+ // 스레드 안전성과 다중 호출 시의 멱등성을 보장하기 위해 인스턴스 락 사용
+ synchronized (this) {
+ // 이미 닫힌 경우 early-return
+ if (this.arena == null || !this.arena.scope().isAlive()) {
+ log.debug("이미 소거 완료되었거나 유효하지 않은 컨테이너입니다.");
+ return;
+ }
+
+ try {
+ // 호출자 측 네이티브 메모리 완벽 소거 & 할당 해제
+ EntLibNativeManager
+ .call(Function.Caller_Secure_Buffer_Wipe)
+ .invokeExact(this.memorySegment, this.memorySegment.byteSize());
+ } catch (Throwable e) {
+ // 네이티브 소거 실패는 치명적 보안 이벤트이기 떄문에 에러 출력
+ log.error("치명적 보안 예외가 발생했습니다!", e);
+ } finally {
+ // 예외 발생 여부와 상관없이 접근 채널은 반드시 닫음
+ this.arena.close();
+ }
+ }
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java
new file mode 100644
index 0000000..91763ef
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java
@@ -0,0 +1,123 @@
+package space.qu4nt.entanglementlib.security.entlibnative;
+
+import org.jetbrains.annotations.NotNull;
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityCritical;
+import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SymbolLookup;
+import java.lang.invoke.MethodHandle;
+import java.util.HashMap;
+import java.util.Map;
+
+/// 해당 클래스를 통해 네이티브 함수를 호출하는 경우, 반드시 [NativeLoader]에 의해
+/// 타겟 네이티브 라이브러리가 시스템에 등록(선행)되어 있어야 합니다.
+///
+/// @author Q. T. Felix
+/// @since 1.1.1
+public final class EntLibNativeManager {
+
+ private static final SymbolLookup lookup;
+ private static final Linker linker = Linker.nativeLinker();
+
+ // 동시성 문제 및 런타임 조작을 원천 차단하기 위한 불변 맵(Immutable Map)
+ private static Map methodHandles;
+
+ // 핫 패스(Hot Path) 최적화를 위한 핵심 MethodHandle 정적 캐시
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private static MethodHandle MH_CALLEE_SECURE_BUFFER_DATA;
+ private static MethodHandle MH_CALLEE_SECURE_BUFFER_LEN;
+ private static MethodHandle MH_CALLEE_SECURE_BUFFER_FREE;
+ private static MethodHandle MH_CALLEE_SECURE_BUFFER_COPY_AND_FREE;
+
+ static {
+ lookup = SymbolLookup.loaderLookup();
+ }
+
+ private EntLibNativeManager() {
+ throw new AssertionError("cannot access");
+ }
+
+ static synchronized void setup() {
+ if (methodHandles != null) return; // 중복 호출 방지
+
+ Map tempMap = new HashMap<>();
+ for (Function function : Function.LOADED)
+ tempMap.put(function, linkExact(function));
+
+ // 맵을 불변 상태로 봉인
+ methodHandles = Map.copyOf(tempMap);
+
+ // 핵심 기능은 O(1)의 Map 탐색 비용조차 없애기 위해 정적 필드에 다이렉트 바인딩
+ MH_CALLEE_SECURE_BUFFER_DATA = methodHandles.get(Function.Callee_Secure_Buffer_Data);
+ MH_CALLEE_SECURE_BUFFER_LEN = methodHandles.get(Function.Callee_Secure_Buffer_Len);
+ MH_CALLEE_SECURE_BUFFER_FREE = methodHandles.get(Function.Callee_Secure_Buffer_Free);
+ MH_CALLEE_SECURE_BUFFER_COPY_AND_FREE = methodHandles.get(Function.SecureBuffer_CopyAndFree);
+ }
+
+ private static MethodHandle linkExact(final @NotNull Function function) {
+ return linker.downcallHandle(
+ lookup.find(function.getFunctionName()).orElseThrow(() ->
+ new ELIBSecurityNativeCritical("네이티브에서 함수 '" + function.getFunctionName() + "'을(를) 찾을 수 없습니다.")),
+ function.getDescriptor()
+ );
+ }
+
+ public static MethodHandle call(final @NotNull Function function) {
+ MethodHandle handle = methodHandles.get(function);
+ if (handle == null)
+ throw new ELIBSecurityNativeCritical("네이티브에서 함수 '" + function.getFunctionName() + "'이(가) 등록되지 않았습니다.");
+ return handle;
+ }
+
+ public static @NotNull SensitiveDataContainer transferNativeBufferBindToContext(
+ final @NotNull SDCScopeContext context,
+ final @NotNull MemorySegment data
+ ) throws ELIBSecurityProcessException {
+ // Rust 측에서 메모리 해제가 완료되었는지 추적하여 Double-Free를 방지하기 위한 플래그
+ boolean isFreedByNative = false;
+
+ try {
+ long len = (long) MH_CALLEE_SECURE_BUFFER_LEN.invokeExact(data);
+
+ // Off-heap 메모리 할당 (OutOfMemoryError 등 예외 발생 가능 구간)
+ SensitiveDataContainer result = context.allocate((int) len);
+
+ // 네이티브에서 직접 복사 및 원본 즉시 소거 (단 1회의 FFI 호출로 압축)
+ long copied = (long) MH_CALLEE_SECURE_BUFFER_COPY_AND_FREE.invokeExact(
+ data,
+ InternalNativeBridge.unwrapMemorySegment(result),
+ len
+ );
+
+ // invokeExact가 예외 없이 통과했다면 Rust의 Box::from_raw에 의해 무조건 소멸
+ isFreedByNative = true;
+
+ // 용량 불일치 등 논리적 오류 검증
+ if (copied != len)
+ throw new ELIBSecurityCritical("네이티브 버퍼 복사 중 크기 불일치 또는 오류가 발생했습니다.");
+
+ return result;
+ } catch (Throwable e) {
+ // context.allocate() 실패 등 Rust로 제어권이 넘어가기 전에 예외가 발생한 경우 메모리 누수 방지
+ if (!isFreedByNative) {
+ try {
+ MH_CALLEE_SECURE_BUFFER_FREE.invokeExact(data);
+ } catch (Throwable ex) {
+ // 원본 예외 유실을 막기 위해 억제된 예외로 병합
+ e.addSuppressed(ex);
+ throw new ELIBSecurityCritical("네이티브 데이터를 소거하는 중 치명적 오류가 발생했습니다!", e);
+ }
+ }
+
+ // 이미 Critical 예외인 경우 그대로 던짐
+ if (e instanceof ELIBSecurityCritical) throw (ELIBSecurityCritical) e;
+ throw new ELIBSecurityProcessException("네이티브 버퍼 획득 및 전송 중 예외가 발생했습니다!", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java
new file mode 100644
index 0000000..430dfa5
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java
@@ -0,0 +1,266 @@
+package space.qu4nt.entanglementlib.security.entlibnative;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.ValueLayout;
+import java.util.ArrayList;
+import java.util.List;
+
+/// NOTE: 사용자는 확장되거나 구체화된 entlib-native 바이너리를 제공했을 수 있음. 따라서 이 클래스 형식은 유효
+@Getter
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class Function {
+
+ protected static final List LOADED = new ArrayList<>();
+
+ // 보안 버퍼 엔드포인트
+ // 피호출자 할당
+ public static final Function Callee_Secure_Buffer_Data = Function.of("entlib_secure_buffer_data", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function Callee_Secure_Buffer_Len = Function.of("entlib_secure_buffer_len", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
+ public static final Function Callee_Secure_Buffer_Free = Function.ofVoid("entlib_secure_buffer_free", ValueLayout.ADDRESS); // 보안 작업에서는 공통적으로 사용
+ // 호출자 할당
+ public static final Function Caller_Secure_Buffer_Wipe = Function.ofVoid("entanglement_secure_wipe", ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+
+ // 데이터 상호 작용을 위한 엔드포인트
+ public static final Function SecureBuffer_Data = Function.of("entlib_secure_buffer_data", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SecureBuffer_Len = Function.of("entlib_secure_buffer_len", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
+ public static final Function SecureBuffer_Free = Function.ofVoid("entlib_secure_buffer_free", ValueLayout.ADDRESS);
+ public static final Function SecureBuffer_View = Function.of("entlib_secure_buffer_view",
+ MemoryLayout.structLayout(
+ ValueLayout.ADDRESS.withName("data"),
+ ValueLayout.JAVA_LONG.withName("len")
+ ),
+ ValueLayout.ADDRESS
+ );
+ public static final Function SecureBuffer_CopyAndFree = Function.of("entlib_secure_buffer_copy_and_free", ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG
+ );
+
+ // Base64 엔드포인트
+ public static final Function Base64_encode = Function.of("entlib_b64_encode_caller_alloc", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function Base64_decode = Function.of("entlib_b64_decode_caller_alloc", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); // todo; err_flag 파라미터 추가 확인 필요
+
+ // Hash 엔드포인트
+ // SHA2
+ public static final Function SHA2_224_New = Function.of("entlib_sha224_new", ValueLayout.ADDRESS);
+ public static final Function SHA2_224_Update = Function.of("entlib_sha224_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA2_224_Finalize = Function.of("entlib_sha224_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA2_224_Free = Function.ofVoid("entlib_sha224_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA2_256_New = Function.of("entlib_sha256_new", ValueLayout.ADDRESS);
+ public static final Function SHA2_256_Update = Function.of("entlib_sha256_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA2_256_Finalize = Function.of("entlib_sha256_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA2_256_Free = Function.ofVoid("entlib_sha256_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA2_384_New = Function.of("entlib_sha384_new", ValueLayout.ADDRESS);
+ public static final Function SHA2_384_Update = Function.of("entlib_sha384_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA2_384_Finalize = Function.of("entlib_sha384_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA2_384_Free = Function.ofVoid("entlib_sha384_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA2_512_New = Function.of("entlib_sha512_new", ValueLayout.ADDRESS);
+ public static final Function SHA2_512_Update = Function.of("entlib_sha512_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA2_512_Finalize = Function.of("entlib_sha512_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA2_512_Free = Function.ofVoid("entlib_sha512_free", ValueLayout.ADDRESS);
+
+ // SHA3
+ public static final Function SHA3_224_New = Function.of("entlib_sha3_224_new", ValueLayout.ADDRESS);
+ public static final Function SHA3_224_Update = Function.of("entlib_sha3_224_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_224_Finalize = Function.of("entlib_sha3_224_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA3_224_Free = Function.ofVoid("entlib_sha3_224_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA3_256_New = Function.of("entlib_sha3_256_new", ValueLayout.ADDRESS);
+ public static final Function SHA3_256_Update = Function.of("entlib_sha3_256_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_256_Finalize = Function.of("entlib_sha3_256_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA3_256_Free = Function.ofVoid("entlib_sha3_256_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA3_384_New = Function.of("entlib_sha3_384_new", ValueLayout.ADDRESS);
+ public static final Function SHA3_384_Update = Function.of("entlib_sha3_384_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_384_Finalize = Function.of("entlib_sha3_384_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA3_384_Free = Function.ofVoid("entlib_sha3_384_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA3_512_New = Function.of("entlib_sha3_512_new", ValueLayout.ADDRESS);
+ public static final Function SHA3_512_Update = Function.of("entlib_sha3_512_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_512_Finalize = Function.of("entlib_sha3_512_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function SHA3_512_Free = Function.ofVoid("entlib_sha3_512_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA3_SHAKE128_New = Function.of("entlib_sha3_shake128_new", ValueLayout.ADDRESS);
+ public static final Function SHA3_SHAKE128_Update = Function.of("entlib_sha3_shake128_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_SHAKE128_Finalize = Function.of("entlib_sha3_shake128_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_SHAKE128_Free = Function.ofVoid("entlib_sha3_shake128_free", ValueLayout.ADDRESS);
+
+ public static final Function SHA3_SHAKE256_New = Function.of("entlib_sha3_shake256_new", ValueLayout.ADDRESS);
+ public static final Function SHA3_SHAKE256_Update = Function.of("entlib_sha3_shake256_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_SHAKE256_Finalize = Function.of("entlib_sha3_shake256_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
+ public static final Function SHA3_SHAKE256_Free = Function.ofVoid("entlib_sha3_shake256_free", ValueLayout.ADDRESS);
+
+ // RNG 엔드포인트
+ public static final Function RNG_HW_Generate = Function.of("entlib_rng_hw_generate", ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
+ public static final Function RNG_HW_Next_Generate = Function.of("entlib_rng_hw_next_generate", ValueLayout.JAVA_BYTE, ValueLayout.ADDRESS);
+ public static final Function RNG_ANU_Generate = Function.of("entlib_rng_anu_generate", ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
+ public static final Function RNG_MIXED_New_With_Strategy = Function.of("entlib_rng_mixed_new_with_strategy", ValueLayout.ADDRESS, ValueLayout.JAVA_BYTE, ValueLayout.ADDRESS);
+ public static final Function RNG_MIXED_New = Function.of("entlib_rng_mixed_new", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+ public static final Function RNG_MIXED_Generate = Function.of("entlib_rng_mixed_generate", ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
+ public static final Function RNG_MIXED_Free = Function.ofVoid("entlib_rng_mixed_free", ValueLayout.ADDRESS);
+
+ // ChaCha20 엔드포인트
+ public static final Function ChaCha20_Process = Function.of("process_chacha20_ffi", ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.JAVA_INT,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG);
+ public static final Function ChaCha20_Poly1305_MAC_Generate = Function.of("generate_poly1305_ffi", ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG);
+ public static final Function ChaCha20_Poly1305_Encrypt = Function.of("chacha20_poly1305_encrypt_ffi", ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG);
+ public static final Function ChaCha20_Poly1305_Decrypt = Function.of("chacha20_poly1305_decrypt_ffi", ValueLayout.ADDRESS,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG);
+
+ private final @NotNull String functionName;
+ private final FunctionDescriptor descriptor;
+
+ public static Function of(final @NotNull String functionName, MemoryLayout returnType, MemoryLayout... args) {
+ Function r = new Function(functionName, FunctionDescriptor.of(returnType, args));
+ LOADED.add(r);
+ return r;
+ }
+
+ public static Function ofVoid(final @NotNull String functionName, MemoryLayout... args) {
+ Function r = new Function(functionName, FunctionDescriptor.ofVoid(args));
+ LOADED.add(r);
+ return r;
+ }
+
+ public static Function[] chain(Function[] source, Function[]... additional) {
+ int size = source.length;
+ for (Function[] specs : additional) {
+ size += specs.length;
+ }
+
+ Function[] result = new Function[size];
+
+ System.arraycopy(source, 0, result, 0, source.length);
+ int currentPosition = source.length;
+
+ for (Function[] specs : additional) {
+ System.arraycopy(specs, 0, result, currentPosition, specs.length);
+ currentPosition += specs.length;
+ }
+
+ return result;
+ }
+
+ public static Function[] withCallerSecureBuffer() {
+ return new Function[]{
+ Callee_Secure_Buffer_Data,
+ Callee_Secure_Buffer_Len,
+ Callee_Secure_Buffer_Free,
+ Caller_Secure_Buffer_Wipe
+ };
+ }
+
+ public static Function[] withCalleeSecureBuffer() {
+ return new Function[]{
+ SecureBuffer_Data,
+ SecureBuffer_Len,
+ SecureBuffer_Free,
+ SecureBuffer_View,
+ SecureBuffer_CopyAndFree
+ };
+ }
+
+ public static Function[] withRNG() {
+ return new Function[]{
+ RNG_HW_Generate,
+ RNG_HW_Next_Generate,
+ RNG_ANU_Generate,
+ RNG_MIXED_New_With_Strategy,
+ RNG_MIXED_New,
+ RNG_MIXED_Generate,
+ RNG_MIXED_Free
+ };
+ }
+
+ public static Function[] withHash(boolean sha2) {
+ if (sha2)
+ return new Function[]{
+ SHA2_224_New,
+ SHA2_224_Update,
+ SHA2_224_Finalize,
+ SHA2_224_Free,
+ SHA2_256_New,
+ SHA2_256_Update,
+ SHA2_256_Finalize,
+ SHA2_256_Free,
+ SHA2_384_New,
+ SHA2_384_Update,
+ SHA2_384_Finalize,
+ SHA2_384_Free,
+ SHA2_512_New,
+ SHA2_512_Update,
+ SHA2_512_Finalize,
+ SHA2_512_Free
+ };
+ return new Function[] {
+ SHA3_224_New,
+ SHA3_224_Update,
+ SHA3_224_Finalize,
+ SHA3_224_Free,
+ SHA3_256_New,
+ SHA3_256_Update,
+ SHA3_256_Finalize,
+ SHA3_256_Free,
+ SHA3_384_New,
+ SHA3_384_Update,
+ SHA3_384_Finalize,
+ SHA3_384_Free,
+ SHA3_512_New,
+ SHA3_512_Update,
+ SHA3_512_Finalize,
+ SHA3_512_Free,
+ SHA3_SHAKE128_New,
+ SHA3_SHAKE128_Update,
+ SHA3_SHAKE128_Finalize,
+ SHA3_SHAKE128_Free,
+ SHA3_SHAKE256_New,
+ SHA3_SHAKE256_Update,
+ SHA3_SHAKE256_Finalize,
+ SHA3_SHAKE256_Free
+ };
+ }
+
+ public static Function[] withChaCha20() {
+ return new Function[]{
+ ChaCha20_Process,
+ ChaCha20_Poly1305_MAC_Generate,
+ ChaCha20_Poly1305_Encrypt,
+ ChaCha20_Poly1305_Decrypt
+ };
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java
new file mode 100644
index 0000000..c30d2fa
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java
@@ -0,0 +1,142 @@
+package space.qu4nt.entanglementlib.security.entlibnative;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityIOException;
+import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+
+// todo 이 클래스 호출 시점에 국제화 파일 로드돼잇을거임
+@Slf4j
+public final class NativeLoader {
+
+ private static volatile boolean loaded = false;
+
+ private NativeLoader() {
+ throw new UnsupportedOperationException("cannot access");
+ }
+
+ private static synchronized void loadSuccess() {
+ loaded = true;
+ EntLibNativeManager.setup();
+ }
+
+ public static synchronized void loadNativeLibrary(final @NotNull EntanglementLibSecurityConfig config) {
+ if (loaded) return;
+
+ try {
+ NativePlatform os = NativePlatform.detectOs();
+ if (os == NativePlatform.UNKNOWN)
+ throw new ELIBSecurityNativeCritical("지원하지 않는 운영체제입니다!");
+
+ String fileName = os.buildFileName(config.getNativeContext().getNativeFilename());
+
+ // 사용자 할당 시 외부 절대 경로 탐색
+ final String dirName = config.getNativeContext().getNativeDirName();
+ Path externalPath = Path.of(dirName);
+ Path exactExternalFile = externalPath.resolve(fileName);
+
+ log.info(exactExternalFile.toString());
+
+ // 사용자가 파일명까지 포함한 절대 경로를 입력했거나, 디렉터리 경로를 입력한 경우 모두 처리
+ if (Files.exists(externalPath) && !Files.isDirectory(externalPath)) {
+ System.load(externalPath.toAbsolutePath().toString());
+ loadSuccess();
+ return;
+ } else if (Files.exists(exactExternalFile)) {
+ System.load(exactExternalFile.toAbsolutePath().toString());
+ loadSuccess();
+ return;
+ }
+
+ // jar 내부 자원 로드
+ String architecture = NativePlatform.detectArchitecture();
+ String resourcePath = dirName + "/" + os.name().toLowerCase() + "/" + architecture + "/" + fileName;
+
+ extractAndLoadFromJar(resourcePath, fileName);
+ loadSuccess();
+ } catch (UnsatisfiedLinkError e) {
+ throw new ELIBSecurityNativeCritical(
+ "파일명이 절대 경로명이 아니거나, " +
+ "네이티브 라이브러리가 VM과 정적으로 연결되지 않거나, " +
+ "호스트 시스템에서 라이브러리를 네이티브 라이브러리 이미지에 매핑할 수 없습니다!");
+ } catch (IllegalCallerException e) {
+ throw new ELIBSecurityNativeCritical("현재 모듈은 네이티브 액세스 권한이 없습니다!");
+ } catch (ELIBSecurityIOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void extractAndLoadFromJar(String resourcePath, String fileName) throws ELIBSecurityIOException {
+ try (InputStream is = NativeLoader.class.getResourceAsStream(resourcePath)) {
+ if (is == null)
+ throw new ELIBSecurityNativeCritical("jar 내부 '" + resourcePath + "' 경로에서 네이티브 라이브러리를 찾을 수 없습니다!");
+
+ Path tempFile = createSecureTempFile(fileName);
+ tempFile.toFile().deleteOnExit();
+
+ Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING);
+ System.load(tempFile.toAbsolutePath().toString());
+
+ } catch (IOException e) {
+ throw new ELIBSecurityIOException("임시 파일 생성 및 복사(덮어쓰기)에 실패했습니다!", e);
+ }
+ }
+
+ private static Path createSecureTempFile(String fileName) throws IOException {
+ int dotIndex = fileName.lastIndexOf('.');
+ String prefix = fileName.substring(0, dotIndex) + "-";
+ String suffix = fileName.substring(dotIndex);
+
+ boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
+
+ if (isPosix) {
+ // POSIX 호환 시스템 (Linux, macOS)
+ Set perms = PosixFilePermissions.fromString("rwx------");
+ return Files.createTempFile(prefix, suffix, PosixFilePermissions.asFileAttribute(perms));
+ } else {
+ // Windows 등 non-POSIX 시스템
+ Path tempPath = Files.createTempFile(prefix, suffix);
+ File tempFile = tempPath.toFile();
+
+ // 실행 권한 설정 (실패 시 즉시 중단)
+ if (!tempFile.setExecutable(true, true)) {
+ tryDelete(tempPath);
+ throw new IOException("임시 파일의 실행 권한(Owner Only)을 설정할 수 없습니다: " + tempPath);
+ }
+
+ // 읽기 권한 설정
+ if (!tempFile.setReadable(true, true)) {
+ tryDelete(tempPath);
+ throw new IOException("임시 파일의 읽기 권한(Owner Only)을 설정할 수 없습니다: " + tempPath);
+ }
+
+ // 쓰기 권한 설정
+ if (!tempFile.setWritable(true, true)) {
+ tryDelete(tempPath);
+ throw new IOException("임시 파일의 쓰기 권한(Owner Only)을 설정할 수 없습니다: " + tempPath);
+ }
+
+ return tempPath;
+ }
+ }
+
+ private static void tryDelete(Path path) {
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
+ log.error("임시 파일 제거 중 예외가 발생했습니다!", e);
+ }
+ }
+}
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativePlatform.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativePlatform.java
new file mode 100644
index 0000000..acd33e5
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativePlatform.java
@@ -0,0 +1,38 @@
+package space.qu4nt.entanglementlib.security.entlibnative;
+
+import java.util.Locale;
+
+public enum NativePlatform {
+
+ WINDOWS("", ".dll"),
+ LINUX("lib", ".so"),
+ MACOS("lib", ".dylib"),
+ UNKNOWN("", "");
+
+ private final String prefix;
+ private final String extension;
+
+ NativePlatform(String prefix, String extension) {
+ this.prefix = prefix;
+ this.extension = extension;
+ }
+
+ public static NativePlatform detectOs() {
+ String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
+ if (osName.contains("win")) return WINDOWS;
+ if (osName.contains("mac") || osName.contains("darwin")) return MACOS;
+ if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) return LINUX;
+ return UNKNOWN;
+ }
+
+ public static String detectArchitecture() {
+ String arch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH);
+ if (arch.contains("amd64") || arch.contains("x86_64")) return "x86_64";
+ if (arch.contains("aarch64") || arch.contains("arm64")) return "aarch64";
+ return arch; // 기타 아키텍처 (필요시 확장)
+ }
+
+ public String buildFileName(String baseName) {
+ return this.prefix + baseName + this.extension;
+ }
+}
\ No newline at end of file
diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java
new file mode 100644
index 0000000..8765f1a
--- /dev/null
+++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java
@@ -0,0 +1,96 @@
+package space.qu4nt.entanglementlib.security.entlibnative;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Set;
+
+import static space.qu4nt.entanglementlib.security.entlibnative.Function.*;
+
+@Getter
+@Setter
+public class NativeSpecContext {
+
+ private String nativeDirName;
+ private String nativeFilename;
+ private Set functions;
+
+ public NativeSpecContext(String nativeDirName, String nativeFilename, Set functions) {
+ this.nativeDirName = nativeDirName;
+ this.nativeFilename = nativeFilename;
+ this.functions = functions;
+ }
+
+ public NativeSpecContext(String nativeDirName, String nativeFilename, Function... functions) {
+ this.nativeDirName = nativeDirName;
+ this.nativeFilename = nativeFilename;
+ this.functions = Set.of(functions);
+ }
+
+ public static NativeSpecContext defaults() {
+ return new NativeSpecContext("/native", "entlib_native_ffi", Set.of(
+ Callee_Secure_Buffer_Data,
+ Callee_Secure_Buffer_Len,
+ Callee_Secure_Buffer_Free,
+ Caller_Secure_Buffer_Wipe,
+ SecureBuffer_Data,
+ SecureBuffer_Len,
+ SecureBuffer_Free,
+ SecureBuffer_View,
+ SecureBuffer_CopyAndFree,
+ Base64_encode,
+ Base64_decode,
+ SHA2_224_New,
+ SHA2_224_Update,
+ SHA2_224_Finalize,
+ SHA2_224_Free,
+ SHA2_256_New,
+ SHA2_256_Update,
+ SHA2_256_Finalize,
+ SHA2_256_Free,
+ SHA2_384_New,
+ SHA2_384_Update,
+ SHA2_384_Finalize,
+ SHA2_384_Free,
+ SHA2_512_New,
+ SHA2_512_Update,
+ SHA2_512_Finalize,
+ SHA2_512_Free,
+ SHA3_224_New,
+ SHA3_224_Update,
+ SHA3_224_Finalize,
+ SHA3_224_Free,
+ SHA3_256_New,
+ SHA3_256_Update,
+ SHA3_256_Finalize,
+ SHA3_256_Free,
+ SHA3_384_New,
+ SHA3_384_Update,
+ SHA3_384_Finalize,
+ SHA3_384_Free,
+ SHA3_512_New,
+ SHA3_512_Update,
+ SHA3_512_Finalize,
+ SHA3_512_Free,
+ SHA3_SHAKE128_New,
+ SHA3_SHAKE128_Update,
+ SHA3_SHAKE128_Finalize,
+ SHA3_SHAKE128_Free,
+ SHA3_SHAKE256_New,
+ SHA3_SHAKE256_Update,
+ SHA3_SHAKE256_Finalize,
+ SHA3_SHAKE256_Free,
+ RNG_HW_Generate,
+ RNG_HW_Next_Generate,
+ RNG_ANU_Generate,
+ RNG_MIXED_New_With_Strategy,
+ RNG_MIXED_New,
+ RNG_MIXED_Generate,
+ RNG_MIXED_Free,
+ ChaCha20_Process,
+ ChaCha20_Poly1305_MAC_Generate,
+ ChaCha20_Poly1305_Encrypt,
+ ChaCha20_Poly1305_Decrypt
+ ));
+ }
+}
diff --git a/src/main/resources/logback.xml b/security/src/main/resources/logback.xml
similarity index 100%
rename from src/main/resources/logback.xml
rename to security/src/main/resources/logback.xml
diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java
new file mode 100644
index 0000000..d37e76c
--- /dev/null
+++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java
@@ -0,0 +1,60 @@
+package space.qu4nt.entanglementlib.security.crypto;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade;
+import space.qu4nt.entanglementlib.security.crypto.encode.Base64;
+import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+@Slf4j
+class Base64Test {
+
+ @Test
+ @DisplayName("Base64 En/Decode")
+ void test() {
+ EntanglementLibSecurityFacade.initialize(
+ EntanglementLibSecurityConfig.create(
+ new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi",
+ Function.Callee_Secure_Buffer_Data,
+ Function.Callee_Secure_Buffer_Len,
+ Function.Callee_Secure_Buffer_Free,
+ Function.Caller_Secure_Buffer_Wipe,
+ Function.Base64_encode,
+ Function.Base64_decode),
+ HeuristicArenaFactory.ArenaMode.CONFINED)
+ );
+
+ final byte[] plaintext = "Hello, World!".getBytes(StandardCharsets.UTF_8);
+ try (SDCScopeContext scope = new SDCScopeContext()) {
+ SensitiveDataContainer input = scope.allocate(plaintext, true);
+ SensitiveDataContainer result = Base64.encode(input);
+ final MemorySegment rms = InternalNativeBridge.unwrapMemorySegment(result);
+
+ byte[] actualCipherBytes = rms.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X
+ byte[] newACBytes = new byte[actualCipherBytes.length];
+ System.arraycopy(actualCipherBytes, 0, newACBytes, 0, actualCipherBytes.length);
+ log.info("Encoded: {}", new String(newACBytes, StandardCharsets.UTF_8));
+
+ SensitiveDataContainer decoded = Base64.decode(result);
+ MemorySegment decResultOpt = InternalNativeBridge.unwrapMemorySegment(decoded);
+ byte[] actualDecBytes = decResultOpt.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X
+ byte[] newADCBytes = new byte[actualDecBytes.length];
+ System.arraycopy(actualDecBytes, 0, newADCBytes, 0, actualDecBytes.length);
+ log.info("Decoded: {}", new String(newADCBytes, StandardCharsets.UTF_8));
+ }
+ }
+}
\ No newline at end of file
diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java
new file mode 100644
index 0000000..518dfcc
--- /dev/null
+++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java
@@ -0,0 +1,123 @@
+package space.qu4nt.entanglementlib.security.crypto;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import space.qu4nt.entanglementlib.core.util.wrapper.Hex;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade;
+import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ChaCha20Test {
+
+ @BeforeAll
+ static void setUp() {
+ // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화
+ EntanglementLibSecurityFacade.initialize(
+ EntanglementLibSecurityConfig.create(
+ new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi",
+ Function.Callee_Secure_Buffer_Data,
+ Function.Callee_Secure_Buffer_Len,
+ Function.Callee_Secure_Buffer_Free,
+ Function.Caller_Secure_Buffer_Wipe,
+ Function.ChaCha20_Poly1305_Encrypt,
+ Function.ChaCha20_Poly1305_Decrypt),
+ HeuristicArenaFactory.ArenaMode.CONFINED)
+ );
+ }
+
+ @Test
+ @DisplayName("ChaCha20 known answer test & 메모리 안전성 검증")
+ void chacha20Test() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
+ // 기댓값
+ final byte[] expected = new byte[]{
+ (byte) 0xd3, 0x1a, (byte) 0x8d, 0x34, 0x64, (byte) 0x8e, 0x60, (byte) 0xdb, 0x7b, (byte) 0x86,
+ (byte) 0xaf, (byte) 0xbc, 0x53, (byte) 0xef, 0x7e, (byte) 0xc2, (byte) 0xa4, (byte) 0xad,
+ (byte) 0xed, 0x51, 0x29, 0x6e, 0x08, (byte) 0xfe, (byte) 0xa9, (byte) 0xe2, (byte) 0xb5,
+ (byte) 0xa7, 0x36, (byte) 0xee, 0x62, (byte) 0xd6, 0x3d, (byte) 0xbe, (byte) 0xa4, 0x5e,
+ (byte) 0x8c, (byte) 0xa9, 0x67, 0x12, (byte) 0x82, (byte) 0xfa, (byte) 0xfb, 0x69, (byte) 0xda,
+ (byte) 0x92, 0x72, (byte) 0x8b, 0x1a, 0x71, (byte) 0xde, 0x0a, (byte) 0x9e, 0x06, 0x0b, 0x29,
+ 0x05, (byte) 0xd6, (byte) 0xa5, (byte) 0xb6, 0x7e, (byte) 0xcd, 0x3b, 0x36, (byte) 0x92,
+ (byte) 0xdd, (byte) 0xbd, 0x7f, 0x2d, 0x77, (byte) 0x8b, (byte) 0x8c, (byte) 0x98, 0x03,
+ (byte) 0xae, (byte) 0xe3, 0x28, 0x09, 0x1b, 0x58, (byte) 0xfa, (byte) 0xb3, 0x24, (byte) 0xe4,
+ (byte) 0xfa, (byte) 0xd6, 0x75, (byte) 0x94, 0x55, (byte) 0x85, (byte) 0x80, (byte) 0x8b, 0x48,
+ 0x31, (byte) 0xd7, (byte) 0xbc, 0x3f, (byte) 0xf4, (byte) 0xde, (byte) 0xf0, (byte) 0x8e, 0x4b,
+ 0x7a, (byte) 0x9d, (byte) 0xe5, 0x76, (byte) 0xd2, 0x65, (byte) 0x86, (byte) 0xce, (byte) 0xc6,
+ 0x4b, 0x61, 0x16
+ };
+ final byte[] expectedTag = new byte[]{
+ 0x1a, (byte) 0xe1, 0x0b, 0x59, 0x4f, 0x09, (byte) 0xe2, 0x6a, 0x7e, (byte) 0x90, 0x2e,
+ (byte) 0xcb, (byte) 0xd0, 0x60, 0x06, (byte) 0x91
+ };
+
+ byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer key = context.allocate(new byte[]{
+ (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87,
+ (byte) 0x88, (byte) 0x89, (byte) 0x8a, (byte) 0x8b, (byte) 0x8c, (byte) 0x8d, (byte) 0x8e, (byte) 0x8f,
+ (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97,
+ (byte) 0x98, (byte) 0x99, (byte) 0x9a, (byte) 0x9b, (byte) 0x9c, (byte) 0x9d, (byte) 0x9e, (byte) 0x9f
+ }, true);
+ SensitiveDataContainer nonce = context.allocate(new byte[]{
+ 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47
+ }, true);
+ SensitiveDataContainer aad = context.allocate(new byte[]{
+ 0x50, 0x51, 0x52, 0x53, (byte) 0xc0, (byte) 0xc1, (byte) 0xc2, (byte) 0xc3, (byte) 0xc4, (byte) 0xc5, (byte) 0xc6, (byte) 0xc7
+ }, true);
+ SensitiveDataContainer result = ChaCha20.encrypt(context, key, nonce, aad, inputData);
+
+ assertNotNull(result, "연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(expected.length + 16, resultSegmentAlias.byteSize(),
+ "암호문 길이가 불일치합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualCipherBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X
+ assertEquals(actualCipherBytes.length, expected.length + 16, "암호문 길이가 불일치합니다.");
+ byte[] newACBytes = new byte[actualCipherBytes.length - 16];
+ System.arraycopy(actualCipherBytes, 0, newACBytes, 0, actualCipherBytes.length - 16);
+ assertEquals(Hex.toHexString(expected), Hex.toHexString(newACBytes),
+ "암호문이 불일치합니다.");
+ newACBytes = new byte[16];
+ System.arraycopy(actualCipherBytes, actualCipherBytes.length - 16, newACBytes, 0, 16);
+ assertEquals(Hex.toHexString(expectedTag), Hex.toHexString(newACBytes),
+ "MAC 태그가 불일치합니다.");
+
+ //
+ // 복호화
+ //
+ SensitiveDataContainer decryptResult = ChaCha20.decrypt(context, key, nonce, aad, result);
+ MemorySegment decResultOpt = InternalNativeBridge.unwrapMemorySegment(decryptResult);
+ assertNotEquals(MemorySegment.NULL, decResultOpt, "네이티브 측 복호화 결과가 null입니다.");
+ assertNotEquals(0, decResultOpt.address(), "네이티브 측 복호화 결과가 유효하지 않습니다.");
+ byte[] actualDecBytes = decResultOpt.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X
+ byte[] newADCBytes = new byte[actualDecBytes.length];
+ System.arraycopy(actualDecBytes, 0, newADCBytes, 0, actualDecBytes.length);
+ assertEquals(Hex.toHexString(inputBytes), Hex.toHexString(newADCBytes), "복호화 결과가 불일치합니다.");
+ } // 스코프 벗어남 -> 컨텍스트 내부 모든 데이터 소거 요청 -> Rust에서 소거
+
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+}
\ No newline at end of file
diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java
new file mode 100644
index 0000000..22b3476
--- /dev/null
+++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java
@@ -0,0 +1,99 @@
+package space.qu4nt.entanglementlib.security.crypto;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException;
+import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade;
+import space.qu4nt.entanglementlib.security.crypto.rng.RNG;
+import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class RNGTest {
+
+ @BeforeAll
+ static void setUp() {
+ // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화
+ EntanglementLibSecurityFacade.initialize(
+ EntanglementLibSecurityConfig.create(
+ new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi",
+ Function.chain(
+ Function.withCalleeSecureBuffer(),
+ Function.withCallerSecureBuffer(),
+ Function.withRNG())),
+ HeuristicArenaFactory.ArenaMode.CONFINED)
+ );
+ }
+
+ @Test
+ @DisplayName("로컬 하드웨어(local hardware) 기반 혼합 난수 생성 및 소거(zeroize) 검증")
+ void hardwareRngLifecycleAndZeroizeTest() throws ELIBSecurityProcessException {
+ final long PQC_KEY_LENGTH = 32; // 양자-내성 암호화(post-quantum cryptography) 키 길이를 가정한 32바이트
+ MemorySegment capturedSegment;
+
+ // 보안 스코프(secure scope) 컨텍스트 생성 및 난수 할당
+ try (SDCScopeContext scope = new SDCScopeContext()) {
+ SensitiveDataContainer sdc = RNG.generateRNG(RNG.LOCAL_HARDWARE, scope, PQC_KEY_LENGTH);
+
+ assertNotNull(sdc, "생성된 민감 데이터 컨테이너(sensitive data container)는 null이 아니어야 합니다.");
+ capturedSegment = InternalNativeBridge.unwrapMemorySegment(sdc);
+
+ // arena 및 메모리 세그먼트 유효성 검증
+ assertTrue(InternalNativeBridge.unwrapArena(sdc).scope().isAlive(), "스코프(scope) 내부에서는 Arena가 활성화 상태여야 합니다.");
+ assertEquals(PQC_KEY_LENGTH, capturedSegment.byteSize(), "생성된 난수의 크기가 요청한 길이와 일치해야 합니다.");
+
+ // 엔트로피 데이터 존재 여부 검증 (모두 0인지 확인)
+ boolean hasNonZero = false;
+ for (long i = 0; i < PQC_KEY_LENGTH; i++) {
+ if (capturedSegment.get(ValueLayout.JAVA_BYTE, i) != 0) {
+ hasNonZero = true;
+ break;
+ }
+ }
+ assertTrue(hasNonZero, "초기화된 난수 버퍼가 모두 0일 수는 없습니다. (엔트로피 부족 의심)");
+ } // try-with-resources 종료 시 scope.close() 호출 -> SDC 소거(zeroize) 자동 수행
+
+ // 스코프 종료 후 자원 파기 검증
+ // SDCScopeContext가 닫히면서 소속된 모든 컨테이너의 아레나가 닫히고 네이티브 포인터가 무효화되어야 함
+ assertFalse(capturedSegment.scope().isAlive(), "컨텍스트 종료 후에는 Arena가 반드시 닫혀야 합니다.");
+
+ // 메모리 영역 강제 접근 시 예외 발생 검증
+ assertThrows(IllegalStateException.class, () -> {
+ capturedSegment.get(ValueLayout.JAVA_BYTE, 0);
+ }, "해제 및 소거(zeroize)된 네이티브 메모리에 접근 시도 시 예외가 발생해야 합니다.");
+ }
+
+ @Test
+ @Disabled
+ @DisplayName("양자 네트워크(quantum network) 기반 혼합 난수 생성 에러 핸들링 검증")
+ void quantumNetworkRngErrorHandlingTest() throws ELIBSecurityProcessException {
+ // 양자 네트워크 특성상 외부 통신 환경에 따라 실패할 수 있어서 생성 성공 여부 또는
+ // 네트워크 타임아웃/파싱 에러 시 ELIBSecurityNativeCritical 예외가 올바르게 전파되는지 검증해야됌
+ try (SDCScopeContext scope = new SDCScopeContext()) {
+ try {
+ SensitiveDataContainer sdc = RNG.generateRNG(RNG.QUANTUM_NETWORK, scope, 64);
+ assertNotNull(sdc);
+ assertTrue(InternalNativeBridge.unwrapArena(sdc).scope().isAlive());
+ } catch (ELIBSecurityNativeCritical e) {
+ // 통신 실패 시 던져지는 예외 메시지가 우리가 정의한 규칙에 부합하는지 확인
+ String msg = e.getMessage();
+ assertTrue(
+ msg.contains("네트워크(network)") || msg.contains("파싱(parsing)") || msg.contains("알 수 없는"),
+ "예상치 못한 네이티브 에러가 발생했습니다: " + msg
+ );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java
new file mode 100644
index 0000000..d092042
--- /dev/null
+++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java
@@ -0,0 +1,190 @@
+package space.qu4nt.entanglementlib.security.crypto;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade;
+import space.qu4nt.entanglementlib.security.crypto.hash.Hash;
+import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.nio.charset.StandardCharsets;
+import java.util.HexFormat;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SHA2HashTest {
+
+ @BeforeAll
+ static void setUp() {
+ // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화
+ EntanglementLibSecurityFacade.initialize(
+ EntanglementLibSecurityConfig.create(
+ new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi",
+ Function.chain(
+ Function.withCalleeSecureBuffer(),
+ Function.withCallerSecureBuffer(),
+ Function.withHash(true))),
+ HeuristicArenaFactory.ArenaMode.CONFINED)
+ );
+ }
+
+ @Test
+ @DisplayName("SHA-224 known answer test & 메모리 안전성 검증")
+ void sha224StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA-224 기댓값
+ final String expectedHex = "72a23dfa411ba6fde01dbfabf3b00a709c93ebf273dc29e2d8b261ff";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha2(224, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(28, resultSegmentAlias.byteSize(),
+ "SHA-224 해시 결과는 정확히 28바이트(224비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA-224 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA-256 known answer test & 메모리 안전성 검증")
+ void sha256StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA-256 기댓값
+ final String expectedHex = "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha2(256, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(32, resultSegmentAlias.byteSize(),
+ "SHA-256 해시 결과는 정확히 32바이트(256비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA-256 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA-384 known answer test & 메모리 안전성 검증")
+ void sha384StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA-384 기댓값
+ final String expectedHex = "5485cc9b3365b4305dfb4e8337e0a598a574f8242bf17289e0dd6c20a3cd44a089de16ab4ab308f63e44b1170eb5f515";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha2(384, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(48, resultSegmentAlias.byteSize(),
+ "SHA-384 해시 결과는 정확히 48바이트(384비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA-384 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA-512 known answer test & 메모리 안전성 검증")
+ void sha512StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA-512 기댓값
+ final String expectedHex = "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha2(512, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(64, resultSegmentAlias.byteSize(),
+ "SHA-512 해시 결과는 정확히 64바이트(512비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA-512 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () -> {
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0);
+ }, "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+}
\ No newline at end of file
diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java
new file mode 100644
index 0000000..ed32ec8
--- /dev/null
+++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java
@@ -0,0 +1,267 @@
+package space.qu4nt.entanglementlib.security.crypto;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig;
+import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade;
+import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory;
+import space.qu4nt.entanglementlib.security.crypto.hash.Hash;
+import space.qu4nt.entanglementlib.security.data.InternalNativeBridge;
+import space.qu4nt.entanglementlib.security.data.SDCScopeContext;
+import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer;
+import space.qu4nt.entanglementlib.security.entlibnative.Function;
+import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.nio.charset.StandardCharsets;
+import java.util.HexFormat;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SHA3HashTest {
+
+ @BeforeAll
+ static void setUp() {
+ // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화
+ EntanglementLibSecurityFacade.initialize(
+ EntanglementLibSecurityConfig.create(
+ new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi",
+ Function.chain(
+ Function.withCalleeSecureBuffer(),
+ Function.withCallerSecureBuffer(),
+ Function.withHash(false))
+ ),
+ HeuristicArenaFactory.ArenaMode.CONFINED)
+ );
+ }
+
+ @Test
+ @DisplayName("SHA3-224 known answer test & 메모리 안전성 검증")
+ void sha3_224StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA3-224 기댓값
+ final String expectedHex = "853048fb8b11462b6100385633c0cc8dcdc6e2b8e376c28102bc84f2";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha3(224, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(28, resultSegmentAlias.byteSize(),
+ "SHA3-224 해시 결과는 정확히 28바이트(224비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SH3A-224 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA3-256 known answer test & 메모리 안전성 검증")
+ void sha3_256StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA3-256 기댓값
+ final String expectedHex = "1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha3(256, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(32, resultSegmentAlias.byteSize(),
+ "SHA3-256 해시 결과는 정확히 32바이트(256비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA3-256 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA3-384 known answer test & 메모리 안전성 검증")
+ void sha3_384StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA3-384 기댓값
+ final String expectedHex = "aa9ad8a49f31d2ddcabbb7010a1566417cff803fef50eba239558826f872e468c5743e7f026b0a8e5b2d7a1cc465cdbe";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha3(384, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(48, resultSegmentAlias.byteSize(),
+ "SHA3-384 해시 결과는 정확히 32바이트(384비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA3-384 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA3-512 known answer test & 메모리 안전성 검증")
+ void sha3_512StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA3-512 기댓값
+ final String expectedHex = "38e05c33d7b067127f217d8c856e554fcff09c9320b8a5979ce2ff5d95dd27ba35d1fba50c562dfd1d6cc48bc9c5baa4390894418cc942d968f97bcb659419ed";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha3(512, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(64, resultSegmentAlias.byteSize(),
+ "SHA3-512 해시 결과는 정확히 32바이트(512비트)여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA3-512 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA3-SHAKE128 known answer test & 메모리 안전성 검증")
+ void sha3_shake128StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA3-SHAKE128 (256비트) 기댓값
+ final String expectedHex = "2bf5e6dee6079fad604f573194ba8426bd4d30eb13e8ba2edae70e529b570cbd";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha3Shake(128, 32, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(32, resultSegmentAlias.byteSize(),
+ "이 테스트에서 SHA3-SHAKE128 해시 결과는 정확히 256비트여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA3-SHAKE128 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+
+ @Test
+ @DisplayName("SHA3-SHAKE256 known answer test & 메모리 안전성 검증")
+ void sha3_shake256StrictTest() throws Throwable {
+ // 테스트 벡터
+ final String inputText = "Hello, World!";
+ // SHA3-SHAKE256 (512비트) 기댓값
+ final String expectedHex = "b3be97bfd978833a65588ceae8a34cf59e95585af62063e6b89d0789f372424e8b0d1be4f21b40ce5a83a438473271e0661854f02d431db74e6904d6c347d757";
+ final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
+
+ MemorySegment resultSegmentAlias;
+
+ // 보안 스코프 내에서 암호화 연산 수행
+ try (SDCScopeContext context = new SDCScopeContext()) {
+ SensitiveDataContainer inputData = context.allocate(inputBytes, true);
+ SensitiveDataContainer result = Hash.sha3Shake(256, 64, context, inputData);
+
+ assertNotNull(result, "해시 연산 결과는 null일 수 없습니다.");
+ resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result);
+
+ // [검증 A] 길이 검증
+ assertEquals(64, resultSegmentAlias.byteSize(),
+ "이 테스트에서 SHA3-SHAKE256 해시 결과는 정확히 128자리여야 합니다.");
+
+ // [검증 B] 무결성 검증 (correctness check)
+ byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE);
+ String actualHex = HexFormat.of().formatHex(actualHashBytes);
+ assertEquals(expectedHex, actualHex,
+ "계산된 해시값이 예상된 SHA3-SHAKE256 KAT(known answer test) 값과 정확히 일치해야 합니다.");
+ }
+
+ // 스코프 종료 후 메모리 보호 메커니즘 검증
+ // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면
+ // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨
+ assertThrows(IllegalStateException.class, () ->
+ resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0),
+ "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다.");
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b1fc284..d219926 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1 +1,5 @@
-rootProject.name = "entanglementlib"
\ No newline at end of file
+rootProject.name = "entanglementlib"
+include("core")
+include("security")
+include("annotations")
+include("annotation-processor")
\ No newline at end of file
diff --git a/src/main/java/space/qu4nt/entanglementlib/CallerResponsibility.java b/src/main/java/space/qu4nt/entanglementlib/CallerResponsibility.java
deleted file mode 100644
index e4f6d12..0000000
--- a/src/main/java/space/qu4nt/entanglementlib/CallerResponsibility.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright © 2025-2026 Quant.
- * Under License "PolyForm Noncommercial License 1.0.0".
- */
-
-package space.qu4nt.entanglementlib;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Target;
-
-/**
- * 보안적 책임은 호출자에게 있음을 알리는 어노테이션입니다.
- * 호출자가 해당 어노테이션이 사용된 멤버 사용 시, 작업 종료 후
- * 반드시 보안 작업이 필요함을 의미합니다.
- *
- * 예를 들어, 이 어노테이션이 (복사본이 아닌) 원본 데이터를 반환하는 메소드에 사용되었고
- * 해당 메소드를 사용하고자 하는 경우 반환받은 데이터를 소거해야 합니다.
- *
- * @author Q. T. Felix
- * @since 1.1.0
- */
-@Documented
-@Target(ElementType.TYPE_USE)
-public @interface CallerResponsibility {
-
- /**
- * 책임 전가의 사유 또는 설명를 정의합니다.
- *
- * @return 책임 전가 사유 또는 설명
- */
- String value() default "";
-
-}
diff --git a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibBootstrap.java b/src/main/java/space/qu4nt/entanglementlib/EntanglementLibBootstrap.java
deleted file mode 100644
index f151508..0000000
--- a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibBootstrap.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright © 2025-2026 Quant.
- * Under License "PolyForm Noncommercial License 1.0.0".
- */
-
-package space.qu4nt.entanglementlib;
-
-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.ApiStatus;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.*;
-
-/**
- * 얽힘 라이브러리의 몇 가지 기능을 외부에서 즉시 호출할 수도 있지만
- * 이 경우 정적 블록에 대한 메모리 할당 및 그에 상응하는 작업의 시간 복잡도가
- * 증가합니다. 이를 해결하기 위해 만들어진 내부 로딩 부트스트랩 클래스입니다.
- *
- * 이 클래스가 내부에서 사용되는 경우는 전달받은 외부 프로젝트(호출자)의 이름을
- * 사용하는 때 이외엔 없으며, 호출되어서도 안 됩니다.
- *
- * @author Q. T. Felix
- * @since 1.1.0
- */
-@Slf4j
-@Getter
-@Setter
-@AllArgsConstructor(access = AccessLevel.PRIVATE)
-public final class EntanglementLibBootstrap {
-
- static {
- log.debug("얽힘 라이브러리(EntanglementLib) 등록");
- }
-
- @ApiStatus.Internal
- private @NotNull String projectName;
-
- @ExternalPattern
- public static EntanglementLibBootstrap registerEntanglementLib(@NotNull String projectName, boolean setBCProviders) {
- if (setBCProviders)
- InternalFactory.registerInternalEntanglementLib();
- return new EntanglementLibBootstrap(projectName);
- }
-
- @ExternalPattern
- public @NotNull SecureRandom getSafeRandom() {
- return InternalFactory.getSafeRandom();
- }
-
- @ApiStatus.Internal
- public static void providerInformation() throws IOException {
- Map>> providerInfo = new LinkedHashMap<>();
-
- for (Provider provider : Security.getProviders()) {
- if (provider == null || provider.getName() == null) continue;
-
- String providerName = provider.getName();
- Map> services = new TreeMap<>();
-
- for (Provider.Service service : provider.getServices()) {
- String type = service.getType();
- String algorithm = service.getAlgorithm();
-
- StringBuilder algoDetails = new StringBuilder(algorithm);
-
- try {
- Field attrField = service.getClass().getDeclaredField("attributes");
- attrField.setAccessible(true);
- @SuppressWarnings("unchecked")
- Map