From ed0451e94ba864aa528ebb11755635ec6d3f453c Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:46:52 +0900 Subject: [PATCH 1/7] =?UTF-8?q?SHA3=20FFI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/ffi/src/lib.rs | 3 +- internal/ffi/src/sha3_ffi.rs | 200 +++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 internal/ffi/src/sha3_ffi.rs diff --git a/internal/ffi/src/lib.rs b/internal/ffi/src/lib.rs index 6edac8e..bc62987 100644 --- a/internal/ffi/src/lib.rs +++ b/internal/ffi/src/lib.rs @@ -1,7 +1,8 @@ pub mod base64_ffi; -mod rng_ffi; +mod rng_ffi; // todo; 보안강화 및 검증 pub mod secure_buffer_ffi; pub mod sha2_ffi; +pub mod sha3_ffi; /// ffi 작업 중 발생할 수 있는 상태 코드 (status code) #[repr(C)] diff --git a/internal/ffi/src/sha3_ffi.rs b/internal/ffi/src/sha3_ffi.rs new file mode 100644 index 0000000..5935507 --- /dev/null +++ b/internal/ffi/src/sha3_ffi.rs @@ -0,0 +1,200 @@ +use crate::FFIStatus; +use core::ptr; +use core::slice; +use entlib_native_core_secure::secure_buffer::SecureBuffer; +use entlib_native_sha3::api::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256}; + +macro_rules! generate_sha3_ffi { + ($struct_type:ty, $new_fn:ident, $update_fn:ident, $finalize_fn:ident, $free_fn:ident) => { + /// 해시 컨텍스트 초기화 (initialize hash context) + #[unsafe(no_mangle)] + pub extern "C" fn $new_fn() -> *mut $struct_type { + let instance = Box::new(<$struct_type>::new()); + Box::into_raw(instance) + } + + /// 데이터 주입 및 상태 업데이트 (update hash state) + /// + /// # Safety + /// - `ctx`는 `$new_fn`을 통해 할당된 유효한 포인터여야 합니다. + /// - `data` 포인터는 최소 `len` 바이트 크기의 읽기 가능한 메모리를 가리켜야 합니다. + /// - 동일한 컨텍스트에 대한 동시 접근 시 스레드-안전(thread-safe)을 보장하기 위해 호출 측에서 동기화해야 합니다. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $update_fn( + ctx: *mut $struct_type, + data: *const u8, + len: usize, + ) -> i32 { + if ctx.is_null() { + return FFIStatus::NullPointerError as i32; + } + if data.is_null() && len > 0 { + return FFIStatus::NullPointerError as i32; + } + + let slice = unsafe { slice::from_raw_parts(data, len) }; + let hasher = unsafe { &mut *ctx }; + + hasher.update(slice); + + FFIStatus::Success as i32 + } + + /// 연산 완료 및 보안 버퍼 반환 (finalize and return secure buffer) + /// + /// 자바 영역으로 데이터를 복사하지 않고, 안전하게 소거되는 `SecureBuffer`의 포인터를 반환합니다. + /// + /// # Safety + /// - `ctx`는 유효한 포인터여야 하며, 호출 후 소유권이 소비(consume)되어 내부 상태가 자동 소거됩니다. + /// - 반환된 `SecureBuffer` 포인터는 자바 측에서 사용이 끝난 직후 반드시 `entlib_secure_buffer_free`를 통해 수동으로 해제되어야 합니다. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $finalize_fn(ctx: *mut $struct_type) -> *mut SecureBuffer { + if ctx.is_null() { + return ptr::null_mut(); + } + + let hasher = unsafe { Box::from_raw(ctx) }; + let digest = hasher.finalize(); // Vec 반환 + + // 다이제스트를 SecureBuffer로 캡슐화하여 힙에 할당 + let secure_buffer = Box::new(SecureBuffer { inner: digest }); + Box::into_raw(secure_buffer) + } + + /// 예외 발생 시 해시 컨텍스트 조기 폐기 (early free on exception) + /// + /// # Safety + /// - `ctx`가 null이 아닐 경우 강제로 소유권을 가져와 메모리를 해제합니다. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $free_fn(ctx: *mut $struct_type) { + if !ctx.is_null() { + unsafe { + drop(Box::from_raw(ctx)); + } + } + } + }; +} + +// SHA3 ffi 엔드포인트 자동 생성 +generate_sha3_ffi!( + SHA3_224, + entlib_sha3_224_new, + entlib_sha3_224_update, + entlib_sha3_224_finalize, + entlib_sha3_224_free +); +generate_sha3_ffi!( + SHA3_256, + entlib_sha3_256_new, + entlib_sha3_256_update, + entlib_sha3_256_finalize, + entlib_sha3_256_free +); +generate_sha3_ffi!( + SHA3_384, + entlib_sha3_384_new, + entlib_sha3_384_update, + entlib_sha3_384_finalize, + entlib_sha3_384_free +); +generate_sha3_ffi!( + SHA3_512, + entlib_sha3_512_new, + entlib_sha3_512_update, + entlib_sha3_512_finalize, + entlib_sha3_512_free +); + +macro_rules! generate_shake_ffi { + ($struct_type:ty, $new_fn:ident, $update_fn:ident, $finalize_fn:ident, $free_fn:ident) => { + /// 해시 컨텍스트 초기화 (initialize hash context) + #[unsafe(no_mangle)] + pub extern "C" fn $new_fn() -> *mut $struct_type { + let instance = Box::new(<$struct_type>::new()); + Box::into_raw(instance) + } + + /// 데이터 주입 및 상태 업데이트 (update hash state) + /// + /// # Safety + /// - `ctx`는 `$new_fn`을 통해 할당된 유효한 포인터여야 합니다. + /// - `data` 포인터는 최소 `len` 바이트 크기의 읽기 가능한 메모리를 가리켜야 합니다. + /// - 동일한 컨텍스트에 대한 동시 접근 시 스레드-안전(thread-safe)을 보장하기 위해 호출 측에서 동기화해야 합니다. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $update_fn( + ctx: *mut $struct_type, + data: *const u8, + len: usize, + ) -> i32 { + if ctx.is_null() { + return FFIStatus::NullPointerError as i32; + } + if data.is_null() && len > 0 { + return FFIStatus::NullPointerError as i32; + } + + let slice = unsafe { slice::from_raw_parts(data, len) }; + let hasher = unsafe { &mut *ctx }; + + hasher.update(slice); + + FFIStatus::Success as i32 + } + + /// XOF 연산 완료 및 보안 버퍼 반환 (finalize and return secure buffer) + /// + /// # Arguments + /// * `ctx` - 해시 컨텍스트 포인터 + /// * `output_len` - 자바 측에서 요구하는 가변 다이제스트의 바이트 길이 (variable output length) + /// + /// # Safety + /// - `ctx`는 유효한 포인터여야 하며, 호출 후 소유권이 소비(consume)되어 내부 상태가 자동 소거됩니다. + /// - 반환된 `SecureBuffer` 포인터는 자바 측에서 사용이 끝난 직후 반드시 `entlib_secure_buffer_free`를 통해 수동으로 해제되어야 합니다. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $finalize_fn( + ctx: *mut $struct_type, + output_len: usize, // XOF를 위한 추가 파라미터 + ) -> *mut SecureBuffer { + if ctx.is_null() { + return ptr::null_mut(); + } + + let hasher = unsafe { Box::from_raw(ctx) }; + + // output_len을 전달하여 원하는 길이만큼 다이제스트 추출 + let digest = hasher.finalize(output_len); + + let secure_buffer = Box::new(SecureBuffer { inner: digest }); + Box::into_raw(secure_buffer) + } + + /// 예외 발생 시 해시 컨텍스트 조기 폐기 (early free on exception) + /// + /// # Safety + /// - `ctx`가 null이 아닐 경우 강제로 소유권을 가져와 메모리를 해제합니다. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $free_fn(ctx: *mut $struct_type) { + if !ctx.is_null() { + unsafe { + drop(Box::from_raw(ctx)); + } + } + } + }; +} + +generate_shake_ffi!( + SHAKE128, + entlib_sha3_shake128_new, + entlib_sha3_shake128_update, + entlib_sha3_shake128_finalize, + entlib_sha3_shake128_free +); +generate_shake_ffi!( + SHAKE256, + entlib_sha3_shake256_new, + entlib_sha3_shake256_update, + entlib_sha3_shake256_finalize, + entlib_sha3_shake256_free +); From a05b4ec7963f36158867f4aaffae49338faa2297 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Tue, 24 Feb 2026 01:49:22 +0900 Subject: [PATCH 2/7] =?UTF-8?q?RNG=20=EB=AA=A8=EB=93=88=20FFI=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20ANU=20QRNG=20API=20=EC=99=84=EC=A0=84?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 + crypto/rng/Cargo.toml | 2 + crypto/rng/benches/anu_qrng_bench.rs | 7 +- crypto/rng/src/anu_qrng.rs | 39 ++++++++--- crypto/rng/src/base_rng.rs | 6 +- crypto/rng/src/mixed.rs | 17 +++-- crypto/rng/tests/anu_qrng_test.rs | 22 +++--- crypto/rng/tests/mixed_rng_test.rs | 8 +-- internal/ffi/Cargo.toml | 1 + internal/ffi/src/rng_ffi.rs | 101 ++++++++++++++++++++++----- 10 files changed, 156 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 62eaea7..6c8376f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ strip = true ### INTERNAL CORE DEPENDENCIES ### entlib-native-ffi = { path = "./internal/ffi", version = "1.1.2-Alpha" } entlib-native-quantum-util = { path = "./internal/quantum-util", version = "1.1.2-Alpha" } +### EXTERNAL DEPENDENCIES ### +tokio = { version = "1", features = ["rt", "process"] } ### INTERNAL CRYPTO DEPENDENCIES ### entlib-native-base64 = { path = "./crypto/base64", version = "1.1.2-Alpha" } entlib-native-constant-time = { path = "./crypto/constant-time", version = "1.1.2-Alpha" } diff --git a/crypto/rng/Cargo.toml b/crypto/rng/Cargo.toml index a5ebc73..94e0eeb 100644 --- a/crypto/rng/Cargo.toml +++ b/crypto/rng/Cargo.toml @@ -7,9 +7,11 @@ license.workspace = true [dependencies] entlib-native-core-secure.workspace = true +tokio.workspace = true [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } +tokio = { workspace = true, features = ["macros"] } [[bench]] name = "anu_qrng_bench" diff --git a/crypto/rng/benches/anu_qrng_bench.rs b/crypto/rng/benches/anu_qrng_bench.rs index 7b16857..a786614 100644 --- a/crypto/rng/benches/anu_qrng_bench.rs +++ b/crypto/rng/benches/anu_qrng_bench.rs @@ -6,6 +6,7 @@ use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_m use entlib_native_rng::anu_qrng::AnuQrngClient; use std::hint::black_box; // Q. T. Felix NOTE: std::hint blackbox use std::time::Duration; +use tokio::runtime::Builder; /// 섀넌 엔트로피(shannon entropy) 계산 함수 /// @@ -51,9 +52,13 @@ fn bench_qrng_throughput(c: &mut Criterion) { BenchmarkId::new("fetch_secure_bytes", len), &len, |b, &size| { + let rt = Builder::new_current_thread() + .enable_all() + .build() + .expect("tokio 런타임 생성 실패"); b.iter(|| { // black_box를 통해 컴파일러의 데드 코드 제거(dead code elimination) 최적화 방지 - let result = AnuQrngClient::fetch_secure_bytes(black_box(size)); + let result = rt.block_on(AnuQrngClient::fetch_secure_bytes(black_box(size))); // 네트워크 실패 시 벤치마크 패닉을 방지하고 에러 코드를 반환하도록 처리 if let Ok(buffer) = result { diff --git a/crypto/rng/src/anu_qrng.rs b/crypto/rng/src/anu_qrng.rs index 5bf0bd9..c0e0b3e 100644 --- a/crypto/rng/src/anu_qrng.rs +++ b/crypto/rng/src/anu_qrng.rs @@ -1,6 +1,7 @@ //! ANU QRNG API(Streaming) //! -//! TLS통신을 통해 작업을 수행하기 떄문에 +//! # Secure Warning +//! TLS통신을 통해 작업을 수행하기 때문에 //! 네트워크 지연 등의 문제가 있습니다. //! //! # Author @@ -8,34 +9,52 @@ use crate::base_rng::RngError; use entlib_native_core_secure::secure_buffer::SecureBuffer; -use std::process::Command; +use std::process::Stdio; use std::str; use std::vec::Vec; +use tokio::process::Command; pub struct AnuQrngClient; impl AnuQrngClient { - pub fn fetch_secure_bytes(length: usize) -> Result { + pub async fn fetch_secure_bytes(length: usize) -> Result { if !(1..=1024).contains(&length) { return Err(RngError::ParseError); } let url = format!( - "https://qrng.anu.edu.au/API/jsonI.php?length={}&type=uint8", + "https://api.quantumnumbers.anu.edu.au?length={}&type=uint8", length ); - // -f (HTTP 오류 시 실패), -L (리다이렉트 추적) let output = Command::new("curl") - .arg("-s") - .arg("-f") - .arg("-L") + .arg("-sS") // silent (진행률 숨김) + 오류 메시지 표시 + .arg("-f") // HTTP 오류 시 실패 + .arg("-L") // 리다이렉트 추적 + .arg("-X") + .arg("GET") + .arg("-H") + .arg(format!( + "x-api-key:{}", + std::env::var("QRNG_ANU_KEY").expect("QRNG_ANU_KEY must be set") + )) + .arg("--connect-timeout") + .arg("10") // 연결 타임아웃: 10초 + .arg("--max-time") + .arg("30") // 전체 요청 타임아웃: 30초 .arg(url) + .stdin(Stdio::null()) // JVM FFI 컨텍스트에서의 stdin 상속 차단 .output() - .map_err(|_| RngError::NetworkFailure)?; + .await + .map_err(|e| RngError::NetworkFailure(format!("curl 실행 실패: {}", e)))?; if !output.status.success() { - return Err(RngError::NetworkFailure); + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(RngError::NetworkFailure(format!( + "HTTP 실패 (code: {}) | stderr: {}", + output.status, + stderr.trim() + ))); } let response_str = str::from_utf8(&output.stdout).map_err(|_| RngError::ParseError)?; diff --git a/crypto/rng/src/base_rng.rs b/crypto/rng/src/base_rng.rs index 2da5f69..2603b5b 100644 --- a/crypto/rng/src/base_rng.rs +++ b/crypto/rng/src/base_rng.rs @@ -24,9 +24,13 @@ pub enum RngError { /// 엔트로피 풀 고갈 또는 하드웨어 응답에 실패했습니다. EntropyDepletion, /// 양자 난수 네트워크 요청(curl)이 실패했습니다. - NetworkFailure, + NetworkFailure(String), /// 양자 난수 API 응답 데이터 파싱에 실패했습니다. ParseError, + /// 잘못된 포인터를 참조합니다. + InvalidPointer, + /// 잘못된 파라미터를 전달했거나 받았습니다. + InvalidParameter, } /// 요청한 길이만큼의 진난수를 포함하는 보안 버퍼를 반환합니다. diff --git a/crypto/rng/src/mixed.rs b/crypto/rng/src/mixed.rs index b3b23d9..72dd4e7 100644 --- a/crypto/rng/src/mixed.rs +++ b/crypto/rng/src/mixed.rs @@ -61,10 +61,19 @@ impl MixedRng { generate_hardware_random_bytes(32)?, generate_hardware_random_bytes(12)?, ), - EntropyStrategy::QuantumNetwork => ( - AnuQrngClient::fetch_secure_bytes(32)?, - AnuQrngClient::fetch_secure_bytes(12)?, - ), + EntropyStrategy::QuantumNetwork => { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| { + RngError::NetworkFailure(format!("tokio 런타임 빌드 실패: {}", e)) + })?; + rt.block_on(async { + let key = AnuQrngClient::fetch_secure_bytes(32).await?; + let nonce = AnuQrngClient::fetch_secure_bytes(12).await?; + Ok::<(SecureBuffer, SecureBuffer), RngError>((key, nonce)) + })? + } }; let mut state = [0u32; 16]; diff --git a/crypto/rng/tests/anu_qrng_test.rs b/crypto/rng/tests/anu_qrng_test.rs index 71d726e..ee4fbe2 100644 --- a/crypto/rng/tests/anu_qrng_test.rs +++ b/crypto/rng/tests/anu_qrng_test.rs @@ -4,12 +4,12 @@ mod tests { use entlib_native_rng::base_rng::RngError; use std::collections::HashSet; - #[test] - fn test_fetch_secure_bytes_valid() { + #[tokio::test] + async fn test_fetch_secure_bytes_valid() { // 정상적인 길이의 난수 요청이 보안 버퍼(secure buffer)에 올바르게 적재되는지 검증 let target_length = 32; - match AnuQrngClient::fetch_secure_bytes(target_length) { + match AnuQrngClient::fetch_secure_bytes(target_length).await { Ok(buffer) => { assert_eq!( buffer.inner.len(), @@ -17,7 +17,7 @@ mod tests { "요청한 길이와 반환된 버퍼의 크기가 일치하지 않습니다." ); } - Err(RngError::NetworkFailure) | Err(RngError::ParseError) => { + Err(RngError::NetworkFailure(_)) | Err(RngError::ParseError) => { // 폐쇄망 환경이거나 ANU 서버의 정책 변경으로 인한 통신 불가 시 테스트를 안전하게 우회 println!( "Skipping network test: ANU QRNG service is currently unreachable or format changed." @@ -32,12 +32,12 @@ mod tests { } } - #[test] - fn test_fetch_secure_bytes_invalid_bounds() { + #[tokio::test] + async fn test_fetch_secure_bytes_invalid_bounds() { // 버퍼 오버플로 및 비정상적 메모리 할당을 방지하기 위한 경계값 검증 // api가 허용하는 1~1024 범위를 벗어난 요청 시 명시적 에러반환 - assert!(AnuQrngClient::fetch_secure_bytes(0).is_err()); - assert!(AnuQrngClient::fetch_secure_bytes(1025).is_err()); + assert!(AnuQrngClient::fetch_secure_bytes(0).await.is_err()); + assert!(AnuQrngClient::fetch_secure_bytes(1025).await.is_err()); } #[test] @@ -66,13 +66,13 @@ mod tests { assert!(AnuQrngClient::parse_json_data(invalid_type_json).is_err()); } - #[test] - fn test_randomness_basic_entropy() { + #[tokio::test] + async fn test_randomness_basic_entropy() { // 생성된 양자 난수(quantum random number)의 기본적인 무작위성(randomness)을 평가 // 추출된 바이트 시퀀스 내의 고유값(unique values) 개수를 확인하여, 통신 오류로 인해 // 0으로만 채워진 배열(zeroed array)이 반환되는 치명적 보안 결함을 방지함 let len = 100; - if let Ok(buffer) = AnuQrngClient::fetch_secure_bytes(len) { + if let Ok(buffer) = AnuQrngClient::fetch_secure_bytes(len).await { let mut unique_values = HashSet::new(); for &byte in &buffer.inner { unique_values.insert(byte); diff --git a/crypto/rng/tests/mixed_rng_test.rs b/crypto/rng/tests/mixed_rng_test.rs index 265bfd2..d210d3f 100644 --- a/crypto/rng/tests/mixed_rng_test.rs +++ b/crypto/rng/tests/mixed_rng_test.rs @@ -34,7 +34,7 @@ mod tests { strategy_name ); } - Err(RngError::NetworkFailure) | Err(RngError::ParseError) => { + Err(RngError::NetworkFailure(_)) | Err(RngError::ParseError) => { // 오프라인 환경 또는 방화벽에 의한 네트워크 차단 시 테스트 우회(skip) println!( "[{}] Skipping initialization test: Network or parsing unavailable", @@ -76,7 +76,7 @@ mod tests { } } Err(RngError::UnsupportedHardware) - | Err(RngError::NetworkFailure) + | Err(RngError::NetworkFailure(_)) | Err(RngError::ParseError) => { println!( "[{}] Skipping length test: Dependency unavailable", @@ -107,7 +107,7 @@ mod tests { ); } Err(RngError::UnsupportedHardware) - | Err(RngError::NetworkFailure) + | Err(RngError::NetworkFailure(_)) | Err(RngError::ParseError) => { println!( "[{}] Skipping randomness test: Dependency unavailable", @@ -141,7 +141,7 @@ mod tests { ); } Err(RngError::UnsupportedHardware) - | Err(RngError::NetworkFailure) + | Err(RngError::NetworkFailure(_)) | Err(RngError::ParseError) => { println!( "[{}] Skipping block counter test: Dependency unavailable", diff --git a/internal/ffi/Cargo.toml b/internal/ffi/Cargo.toml index dbdbb67..b6795ff 100644 --- a/internal/ffi/Cargo.toml +++ b/internal/ffi/Cargo.toml @@ -20,6 +20,7 @@ strip = true panic = "abort" [dependencies] +tokio.workspace = true entlib-native-base64.workspace = true entlib-native-constant-time.workspace = true entlib-native-core-secure.workspace = true diff --git a/internal/ffi/src/rng_ffi.rs b/internal/ffi/src/rng_ffi.rs index 9e3a418..434a452 100644 --- a/internal/ffi/src/rng_ffi.rs +++ b/internal/ffi/src/rng_ffi.rs @@ -1,36 +1,42 @@ //! 난수 생성기 FFI 브릿지 모듈 //! -//! `base_rng` (하드웨어 TRNG)와 `mixed` (ChaCha20 혼합 RNG)를 Java/Kotlin 네이티브 환경으로 -//! 안전하게 노출합니다. ANU QRNG 네트워크 엔트로피도 전략 선택으로 사용 가능. +//! [entlib_native_rng::base_rng], [entlib_native_rng::mixed], [entlib_native_rng::base_rng::anu_qrng] +//! 모듈을 모두 Java로 안전하게 노출합니다. //! -//! # Security +//! # security //! - 모든 민감 상태는 `MixedRng::Drop` + `SecureBuffer::Drop`에 의해 강제 zeroize -//! - FFI 경계에서 철저한 null 체크 + 에러 코드 매핑 -//! - Rust 2024 에디션 완벽 호환 (unsafe-op-in-unsafe-fn 해결) +//! - anu qrng는 네트워크 호출이므로 `NetworkFailure`/`ParseError` 처리 필수 +//! - heap 메모리 누수를 방지하기 위한 버퍼 해제 함수 추가 //! //! # Author //! Q. T. Felix use core::ptr; use entlib_native_core_secure::secure_buffer::SecureBuffer; +use entlib_native_rng::anu_qrng::AnuQrngClient; use entlib_native_rng::base_rng::{RngError, generate_hardware_random_bytes, next_generate}; use entlib_native_rng::mixed::{EntropyStrategy, MixedRng}; use std::boxed::Box; -/// FFI 에러 코드 매핑 (모든 RngError variant + FFI 전용 코드 커버) +/// ffi 에러 코드 매핑 #[inline(always)] fn map_rng_error(err: RngError) -> u8 { match err { RngError::UnsupportedHardware => 1, RngError::EntropyDepletion => 2, - RngError::NetworkFailure => 4, + RngError::InvalidPointer => 3, + RngError::NetworkFailure(msg) => { + eprintln!("[ENTLIB] RNG NetworkFailure: {}", msg); // 일단 네트워크 문제 부터 파악 + 4 + } RngError::ParseError => 5, + RngError::InvalidParameter => 6, } } -// ================================================ -// 하드웨어 진난수 생성기 (Hardware TRNG) FFI -// ================================================ +// +// 하드웨어 진난수 생성기 (hardware trng) ffi +// #[unsafe(no_mangle)] pub unsafe extern "C" fn entlib_rng_hw_generate( @@ -59,7 +65,7 @@ pub unsafe extern "C" fn entlib_rng_hw_generate( #[unsafe(no_mangle)] pub unsafe extern "C" fn entlib_rng_hw_next_generate(buf: *mut SecureBuffer) -> u8 { if buf.is_null() { - return 3; // Invalid pointer + return map_rng_error(RngError::InvalidPointer); } let buffer = unsafe { &mut *buf }; @@ -69,9 +75,55 @@ pub unsafe extern "C" fn entlib_rng_hw_next_generate(buf: *mut SecureBuffer) -> } } -// ================================================ -// 혼합 난수 생성기 (Mixed RNG with ChaCha20) FFI -// ================================================ +// +// anu 양자 난수 생성기 (quantum rng) ffi +// + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_anu_generate( + len: usize, + err_flag: *mut u8, +) -> *mut SecureBuffer { + if !err_flag.is_null() { + unsafe { + *err_flag = 0; + } + } + + let rt = match tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + { + Ok(rt) => rt, + Err(e) => { + if !err_flag.is_null() { + unsafe { + *err_flag = map_rng_error(RngError::NetworkFailure(format!( + "tokio 런타임 빌드 실패: {}", + e + ))); + } + } + return ptr::null_mut(); + } + }; + + match rt.block_on(AnuQrngClient::fetch_secure_bytes(len)) { + Ok(buffer) => Box::into_raw(Box::new(buffer)), + Err(e) => { + if !err_flag.is_null() { + unsafe { + *err_flag = map_rng_error(e); + } + } + ptr::null_mut() + } + } +} + +// +// 혼합 난수 생성기 (mixed rng with chacha20) ffi +// #[unsafe(no_mangle)] pub unsafe extern "C" fn entlib_rng_mixed_new_with_strategy( @@ -90,8 +142,8 @@ pub unsafe extern "C" fn entlib_rng_mixed_new_with_strategy( _ => { if !err_flag.is_null() { unsafe { - *err_flag = 3; - } // Invalid strategy + *err_flag = map_rng_error(RngError::InvalidParameter); + } } return ptr::null_mut(); } @@ -130,7 +182,7 @@ pub unsafe extern "C" fn entlib_rng_mixed_generate( if rng_ptr.is_null() { if !err_flag.is_null() { unsafe { - *err_flag = 3; + *err_flag = map_rng_error(RngError::InvalidPointer); } } return ptr::null_mut(); @@ -153,6 +205,19 @@ pub unsafe extern "C" fn entlib_rng_mixed_generate( #[unsafe(no_mangle)] pub unsafe extern "C" fn entlib_rng_mixed_free(rng_ptr: *mut MixedRng) { if !rng_ptr.is_null() { - let _ = unsafe { Box::from_raw(rng_ptr) }; // Drop → zeroize 보장 + // drop -> zeroize 보장 + let _ = unsafe { Box::from_raw(rng_ptr) }; + } +} + +// +// 보안 버퍼 (secure buffer) ffi +// + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_buffer_free(buf_ptr: *mut SecureBuffer) { + if !buf_ptr.is_null() { + // drop -> zeroize 보장 및 러스트 힙 메모리 반환 + let _ = unsafe { Box::from_raw(buf_ptr) }; } } From c9a7c2899bd91f35aa929dd75ebc2698f49397af Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Fri, 27 Feb 2026 00:00:40 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGES.md | 23 ---------------- AGES_EN.md | 23 ---------------- COMPLIANCE.md | 3 +++ COMPLIANCE_EN.md | 3 +++ INTRODUCTION.md | 3 +++ INTRODUCTION_EN.md | 3 +++ NIST-ACVP.md | 3 --- README.md | 56 ++++++++++++++++++++++++++++++++++----- README_EN.md | 66 +++++++++++++++++++++++++++++++++++++--------- 9 files changed, 115 insertions(+), 68 deletions(-) delete mode 100644 AGES.md delete mode 100644 AGES_EN.md create mode 100644 COMPLIANCE.md create mode 100644 COMPLIANCE_EN.md delete mode 100644 NIST-ACVP.md diff --git a/AGES.md b/AGES.md deleted file mode 100644 index 6bf76ae..0000000 --- a/AGES.md +++ /dev/null @@ -1,23 +0,0 @@ -# 초기 공개 버전들에서 - -> [English AGES](AGES_EN.md) - -초기에 이 Rust 네이티브 브릿지 모듈은 [얽힘 라이브러리의 BouncyCastle low-level API 브랜치](https://github.com/Quant-Off/entanglementlib/tree/exp-bc-lightweight-api/src/main/java/space/qu4nt/entanglementlib/experimental/crypto)에서 첫 구현된 `Project Panama API` 기반 네이티브 키 관리 기능([`EntLibCryptoKey.java`](https://github.com/Quant-Off/entanglementlib/blob/exp-bc-lightweight-api/src/main/java/space/qu4nt/entanglementlib/experimental/crypto/key/EntLibCryptoKey.java))을 강화하기 위해 마련됐습니다. 관리 및 소거 기능을 Heap이 아닌 Off-Heap에서 처리하여 보안 및 효율을 강화하고자 했기 때문입니다. - -그리고 해당 기능은 `EntanglementLib 1.1.0` 릴리즈에서 [민감 데이터 컨테이너](https://docs.qu4nt.space/docs/projects/entanglementlib/sensitive-data-container)기능으로 강화되어 성공적으로 출시되었습니다. 구체적으로, 이 네이티브 라이브러리에 `zeroize` 크레이트를 사용하여 소거 로직이 구현되었었습니다. 이에 얽힘 라이브러리 초기 릴리즈에 존재하던 [`KeyDestroyHelper.java`](https://github.com/Quant-Off/entanglementlib/blob/exp-bc-lightweight-api/src/main/java/space/qu4nt/entanglementlib/security/KeyDestroyHelper.java)와 같은 꽤 난잡한 클래스들을 뜯어고쳐 성공적으로 최적화하고 강화하는 데 성공했습니다. - -다만 몇 가지 문제가 발생합니다. 얽힘 라이브러리는 외부 라이브러리(의존성)로부터 많은 보안 기능을 끌어 쓰고 있었던 것이 핵심이라 할 수 있습니다. 그 중에서도 `BouncyCastle`의존성은 얽힘 라이브러리에서 제공하는 모든 알고리즘에 대해 내부 로직을 책임졌습니다. 최신 릴리즈를 공개함과 동시에 TODO를 통해 의존성을 줄이겠다 언급했고, 네이티브 브릿지의 기능을 하나씩 만들어나가고자 했습니다만, Rust 측에서 필요한 기능을 구현하려면 또 다시 외부 의존성을 사용해야 한다는 역설적 문제에 부딪혔습니다. 아무래도 혼자 그 많은 기능을 구현하는 데에는 꽤 시간이 들 테니까요. - -따라서 네이티브 브릿지를 구현하며 우선적으로 '필요한 기능은 의존성(크레이트)을 적극적으로 사용하되, 핵심 철학인 "군사적 보안"에 미치게끔 해야 한다.' 라는 신념을 가지기도 했습니다만, 외부 의존성 사용은 생각보다 저의 자유를 크게 해쳤습니다. 안전하지 않은 것이 아닙니다만, 얽힘 라이브러리에 딱 틀어맞는 기능을 구현하는 데 애먹었은게 가장 큽니다. - -잔가지 생각들은 버리고 보안에 전념하고 싶었고, 이 바램을 이루기 위해 `1.0.0` 공개 후 얼마 안 가 `1.1.0` 릴리즈 개발을 시작했습니다. 버전 하나 올리는게 대수라고 생각하실 수도 있지만 제 생각은 꽤 다릅니다. - -# 다음 릴리즈에서 - -이제 얽힘 라이브러리는 매우매우 엄격한 보안 로직을 갖출 준비를 시작합니다. 이 네이티브 라이브러리에선 군사급, 대규모 엔터프라이즈 등 핵심 보안 철학을 가진 로직이 모두 재탄생됩니다. - -`Base64` 인/디코딩이나 단순 상수-시간 비트 연산, 난수 생성, 고전 암호화 알고리즘 등의 핵심 로직들을 외부 의존성 없이 개발합니다. 단순하지만 정밀한 흐름을 가진 로직을 개발함으로써 많은 인프라 보안에 사용되었으면 좋겠습니다만, 저는 딱히 말하는 능력이 있진 않아 아쉽습니다. - -아시다시피 `1.0.0` 릴리즈의 네이티브 라이브러리는 매우 난잡했으며, 일단 보안성 강화는 되었으나 모두 최적화되지 않았었죠. ACVP 인증에 한참이던 SLH-DSA 알고리즘 구현도 볼품없이 작성되어 그냥 혼돈 그 자체인 코드였습니다. 따라서, 기능을 최적화할 겸 꽤 문제가 많은 코드를 엎어버리고, 루트를 가상 매니페스트(virtual manifest)화하여 얽힘 라이브러리와 브릿징하려고 합니다. - -네이티브 라이브러리의 `1.1.0` 릴리즈(또는 그 이상의 릴리즈)는 얽힘 라이브러리의 `1.1.0` 업데이트 처럼 아주 큰 변화를 가질 예정입니다. 큰 변경에 따라 자연스럽게 얽힘 라이브러리에도 큰 변화가 있을 예정입니다. 그렇게 릴리즈를 출시하고 나면, 두 라이브러리 모두 매우 같은 릴리즈 버전을 가지게 됩니다. \ No newline at end of file diff --git a/AGES_EN.md b/AGES_EN.md deleted file mode 100644 index ff99f2a..0000000 --- a/AGES_EN.md +++ /dev/null @@ -1,23 +0,0 @@ -# In Early Public Versions - -> [Korean AGES](AGES.md) - -Initially, this Rust native bridge module was prepared to enhance the `Project Panama API`-based native key management feature ([`EntLibCryptoKey.java`](https://github.com/Quant-Off/entanglementlib/blob/exp-bc-lightweight-api/src/main/java/space/qu4nt/entanglementlib/experimental/crypto/key/EntLibCryptoKey.java)) first implemented in the [EntanglementLib's BouncyCastle low-level API branch](https://github.com/Quant-Off/entanglementlib/tree/exp-bc-lightweight-api/src/main/java/space/qu4nt/entanglementlib/experimental/crypto). This was because we wanted to enhance security and efficiency by handling management and erasure functions in Off-Heap rather than Heap. - -And that feature was successfully released as the [Sensitive Data Container](https://docs.qu4nt.space/en/docs/projects/entanglementlib/sensitive-data-container) feature in the `EntanglementLib 1.1.0` release. Specifically, erasure logic was implemented in this native library using the `zeroize` crate. Accordingly, we succeeded in successfully optimizing and enhancing quite messy classes like [`KeyDestroyHelper.java`](https://github.com/Quant-Off/entanglementlib/blob/exp-bc-lightweight-api/src/main/java/space/qu4nt/entanglementlib/security/KeyDestroyHelper.java) that existed in the initial release of the EntanglementLib. - -However, a few problems arose. It can be said that the core of the EntanglementLib was pulling many security features from external libraries (dependencies). Among them, the `BouncyCastle` dependency was responsible for the internal logic for all algorithms provided by the EntanglementLib. While releasing the latest release, I mentioned in the TODO that I would reduce dependencies, and I wanted to build the features of the native bridge one by one, but I ran into the paradoxical problem that I had to use external dependencies again to implement the necessary features on the Rust side. It would probably take quite a while to implement all those features alone. - -Therefore, while implementing the native bridge, I initially held the belief that 'necessary features should actively use dependencies (crates), but they must meet the core philosophy of "military-grade security".' However, using external dependencies greatly harmed my freedom more than I thought. It's not that it's unsafe, but the biggest issue was the difficulty in implementing features that fit perfectly into the EntanglementLib. - -I wanted to discard trivial thoughts and focus on security, and to achieve this wish, I started developing the `1.1.0` release shortly after the `1.0.0` release. You might think that bumping up a version is no big deal, but my thoughts are quite different. - -# In the Next Release - -Now, the EntanglementLib begins preparations to have very, very strict security logic. In this native library, logic with core security philosophies such as military-grade and large-scale enterprise will all be reborn. - -Core logic such as `Base64` encoding/decoding, simple constant-time bitwise operations, random number generation, and classical encryption algorithms will be developed without external dependencies. I hope that by developing logic with a simple but precise flow, it will be used in many infrastructure securities, but it is a pity that I do not have much speaking ability. - -As you know, the native library of the `1.0.0` release was very messy, and although security was enhanced, it was not optimized at all. The SLH-DSA algorithm implementation, which was in the midst of ACVP certification, was also written poorly and was just chaos itself. Therefore, to optimize features and overhaul the code that has quite a few problems, I intend to virtualize the root manifest and bridge it with the EntanglementLib. - -The `1.1.0` release (or higher releases) of the native library will have very large changes like the `1.1.0` update of the EntanglementLib. Naturally, there will be large changes in the EntanglementLib following the large changes. Once the release is launched like that, both libraries will have the very same release version. \ No newline at end of file diff --git a/COMPLIANCE.md b/COMPLIANCE.md new file mode 100644 index 0000000..0c06d12 --- /dev/null +++ b/COMPLIANCE.md @@ -0,0 +1,3 @@ +# 인증 및 규정 준수 사항 + +작업 진행 중. \ No newline at end of file diff --git a/COMPLIANCE_EN.md b/COMPLIANCE_EN.md new file mode 100644 index 0000000..8c8e769 --- /dev/null +++ b/COMPLIANCE_EN.md @@ -0,0 +1,3 @@ +# Certification or Compliance + +In Progressss... \ No newline at end of file diff --git a/INTRODUCTION.md b/INTRODUCTION.md index de16c49..f1282de 100644 --- a/INTRODUCTION.md +++ b/INTRODUCTION.md @@ -2,6 +2,9 @@ > [English INTRODUCTION](INTRODUCTION_EN.md) +> [!WARNING] +> 이 문서의 내용은 현재 `entlib-native`의 기능과 사뭇 다를 수 있습니다! + [얽힘 라이브러리](https://github.com/Quant-Off/entanglementlib)의 핵심 로직은 모두 이 [Rust 기반 네이티브 라이브러리](https://github.com/Quant-Off/entlib-native)에서 진행됩니다. 이 문서에선 편하게 "네이티브"라고 부르겠습니다. 이 문서에서, 이 네이티브가 얽힘 라이브러리와 어떻게 동작하는지와, 보안 작업을 어떻게 수행하는지 기술적으로 정확히 짚어드리고 싶지만 이 내용은 매-우 방대하기 떄문에 따로 [우리 문서 사이트](https://docs.qu4nt.space/docs/projects/entanglementlib/entlib-native)에 세밀히 정리해두겠습니다. diff --git a/INTRODUCTION_EN.md b/INTRODUCTION_EN.md index 38200e3..09c2dcf 100644 --- a/INTRODUCTION_EN.md +++ b/INTRODUCTION_EN.md @@ -2,6 +2,9 @@ > [Korean INTRODUCTION](INTRODUCTION.md) +> [!WARNING] +> The content of this document may differ significantly from the current functionality of `entlib-native`! + The core logic of the [EntanglementLib](https://github.com/Quant-Off/entanglementlib) is all processed within this [Rust-based native library](https://github.com/Quant-Off/entlib-native). In this document, we will simply refer to it as "Native". In this document, I would like to technically pinpoint exactly how this Native works with the EntanglementLib and how it performs security operations, but since this content is extremely vast, I will organize it in detail separately on [our documentation site](https://docs.qu4nt.space/en/docs/projects/entanglementlib/entlib-native). diff --git a/NIST-ACVP.md b/NIST-ACVP.md deleted file mode 100644 index b43d26f..0000000 --- a/NIST-ACVP.md +++ /dev/null @@ -1,3 +0,0 @@ -프로젝트 초기화 후 진행 예정입니다. - -todo: project release and... \ No newline at end of file diff --git a/README.md b/README.md index 611eca3..7cde4ff 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,57 @@ > [이 라이브러리는 무슨 기능을 할까요?](INTRODUCTION.md) 기술에 대한 세부적 설명은 [퀀트 팀 공개 문서](https://docs.qu4nt.space/docs/projects/entanglementlib/entlib-native) 에서 확인할 수 있습니다. -[Rust의 소유권 개념](https://doc.rust-kr.org/ch04-00-understanding-ownership.html)은 저의 흥미를 유발하는 데 완벽하게 성공했고, 복합적인 Rust만의 특색있는 개발 방법(컴파일러의 메모리 안정성, 하드웨어 수준의 제어 등...)을 통해 [얽힘 라이브러리](https://github.com/Quant-Off/entanglementlib)를 안전하게 구현하는 데 목표를 두고자 했습니다. +[얽힘 라이브러리(EntanglementLib)](https://github.com/Quant-Off/entanglementlib)의 핵심적 기능은 Rust 기반의 네이티브 라이브러리에 있습니다. 모든 보안 연산을 이 네이티브에서 수행하는 겁니다. -얽힘 라이브러리 내에서 [Linker API (JEP389)](https://openjdk.org/jeps/389)를 사용하여 구현됩니다. 이 모듈은 전통적 방법이지만 어렵고 불안정한 JNI(Java Native Interface)로 네이티브 메소드 호출 작업을 수행하는 것을 넘어 코드와 데이터 복사 오버헤드 없이 Java와 Rust가 동일한 메모리 주소를 공유할 수 있도록 돕습니다. +EntanglementLib의 보안 기능을 완벽히 수행하기 위한 네이티브 베이스 언어는 Rust가 가장 잘 어울립니다. 이 언어의 가장 큰 장점은 성능 저하 없이 메모리 안정성을 보장하는 거예요. 세부적으로 [소유권 개념(Ownership)](https://doc.rust-kr.org/ch04-00-understanding-ownership.html)은 자원 관리를 용이하게 하고, **데이터 경쟁 없는 동시성 기능**은 통해 멀티 스레드 환경에서도 보안성을 강화해줍니다. Python이나 JPMS(Java Platform Module System)와 일관된 모듈 관리, 캡슐화가 간편한 등, 언어 자체가 유연한 특성을 가지고 있으며 FFI(Foreign Function Interface)로 Java와 간편히 연결되는 것은 충분히 매력으로 다가옵니다. -> [이 문서](AGES.md)에서 최초 공개 버전부터 이 버전까지의 일대기를 확인하실 수 있습니다. +EntanglementLib의 모든 보안 연산은 이 네이티브에서 수행되죠. 구체적으로 다음의 기능을 제공합니다. + +- [X] 하드웨어 진난수, 혼합 난수, 양자난수(Quantum RNG) 생성 +- [X] AEAD 암호화(ChaCha20) +- [X] 메모리 소거를 보장하는 보안 버퍼(Secure Buffer) +- [X] 상수-시간(Constant-Time) 연산 +- [X] 해시(SHA2, SHA3, SHAKE 포함) + +각 기능은 개별 크레이트로 분리되어 관리됩니다. 루트는 가상 매니페스트로 구성되어 있어 하위 크레이트를 관리하기 용이하죠. 또한 Java 측에서 이 네이티브를 사용할 때 잘못된 호출을 원천적으로 차단하기 위해 FFI 함수를 구현한 크레이트가 존재합니다. 이 크레이트는 Java 측에서 사용되어야 할 주요 함수를 전달하는 용도로 사용됩니다. + +이 네이티브 라이브러리는 핵심 보안 기능 구현에 있어 외부 의존성(크레이트)을 사용하지 않습니다. 다르게 말해 외부로부터 들여오는 모든 자원을 기본적으로 신뢰하지 않는다는 겁니다. 이런 개발 철학은 **Zero Trust 원칙**을 지지하고, 이렇게 만들어진 하나의 결과물(얽힘 라이브러리)은 폐쇄 환경에서도 원활히 동작하게 됩니다. 이는 **Air-Gapped Ready 라는 원칙**에 부합합니다. + +궁극적으로 이 네이티브는 엄격한 환경에서 자란 귀중한 자원으로서, 얽힘 라이브러리에서 적극적으로 안전하게 사용됩니다. + +## 향후 계획 + +이 네이티브는 아직 갈 길이 멉니다. 지원되는 고전적 암호화 알고리즘 모듈을 다양하게 구현해야 합니다. + +- [ ] AES(128, 192, 256) +- [ ] ARIA(128, 192, 256) +- [ ] RSA(2048, 4096, 8192) +- [ ] ED25519, ED448 서명 +- [ ] X25519, X448 키 합의 + +이 뿐만 아니라 HMAC, HKDF 등의 암호학적 필수 기능도 제공되어야 합니다. + +양자-내성 암호화(Post-Quantum Cryptography, PQC) 알고리즘은 다음의 목표를 가집니다. + +- [ ] [FIPS 203(Module Lattice-based Key Encapsulate Mechanism, ML-KEM)](https://csrc.nist.gov/pubs/fips/203/final) +- [ ] [FIPS 204(Module Lattice-based Digital Signature Algorithm, ML-DSA)](https://csrc.nist.gov/pubs/fips/204/final) +- [ ] [FIPS 205(Stateless Hash-based Digital Signature Algorithm, SLH-DSA)](https://csrc.nist.gov/pubs/fips/205/final) + +위 PQC 알고리즘이 구현되면 다음의 TLS 기능도 제공되어야 합니다. + +- [ ] TLS 1.3 +- [ ] [`draft-ietf-tls-ecdhe-mlkem`](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/)에 따른 X25519MLKEM768 + +PKIX나 JWT 및 CWT, OTP 등, 아직 갈 길이 멀다는 것이 실감됩니다. + +## 인증 및 규정 준수 필요 + +구현 뿐만이 아닙니다. 이 네이티브에서 구현되는 모든 기능은 국제적 인증 기관이 명시한 기능의 보안 구현(명세) 상황을 완전히 따라야 하고, 정식적인 인증을 받아야 합니다. 그 전까진 어떤 알고리즘도 '안전'하다고 판단하진 않습니다. 숨겨진 변수는 언제든 나타나기 마련이니까요. + +따라서 이 네이티브의 모든 기능을 사용하신다면 반드시 '살험적(experimental)' 기능으로 제공하거나, 사용하시길 바랍니다. + +> [!NOTE] +> 엄격한 인증 및 규정 심사를 통과한 기능은 즉각적으로 업데이트히겠습니다. [이 문서](COMPLIANCE.md)에서 해당 정보를 확인할 수 있도록 하겠습니다. # 영감 및 기여 @@ -17,10 +63,6 @@ > [!TIP] > 여러분의 피드백은 언제나 아주 큰 힘이 됩니다. 이 프로젝트에 기여하고자 한다면 [이 곳](CONTRIBUTION.md)을 참고해주세요! -# Alpha 버전 - -이 네이티브 라이브러리는 `1.1.x` 릴리즈 출시에 대한 준비를 마쳐도 곧바로 출시되진 않습니다. 이에 세밀하고 또 정밀하게 코드를 검토하고, 그 환경을 원활히 구축하기 위해 지금 이 시점에서 알파 버전으로 우선 공개하겠습니다. - # 벤치마킹 이 네이티브 라이브러리의 벤치마킹은 `criterion` 크레이트를 통해 진행됩니다. 자세한 각 벤치마킹 결과는 [benchmarks 디렉토리 하위](benchmarks)에서 확인하실 수 있습니다. \ No newline at end of file diff --git a/README_EN.md b/README_EN.md index 053f664..07307ea 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,26 +1,68 @@ # EntanglementLib: Native Bridge -> [Korean README](README.md) +> [English README](README_EN.md) -> [What does this library do?](INTRODUCTION.md) Detailed technical explanations can be found in the [Quant Team Public Documentation](https://docs.qu4nt.space/en/docs/projects/entanglementlib/entlib-native). +> [What does this library do?](INTRODUCTION.md) Detailed technical explanations can be found in the [Quant Team Public Documentation](https://docs.qu4nt.space/docs/projects/entanglementlib/entlib-native). -[Rust's concept of ownership](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html) succeeded perfectly in sparking my interest, and I aimed to safely implement the [EntanglementLib](https://github.com/Quant-Off/entanglementlib/blob/master/README_EN.md) through complex development methods unique to Rust (compiler's memory safety, hardware-level control, etc...). +The core functionality of the [EntanglementLib](https://github.com/Quant-Off/entanglementlib/blob/master/README_EN.md) resides in the Rust-based native library. All security operations are performed entirely within this native component. -It is implemented using the [Linker API (JEP389)](https://openjdk.org/jeps/389) within the EntanglementLib. This module goes beyond performing native method calls with the traditional but difficult and unstable JNI (Java Native Interface) and helps Java and Rust share the same memory address without code and data copy overhead. +Rust is the most suitable native base language for perfectly executing EntanglementLib’s security features. Its greatest strength is guaranteeing memory safety without any performance penalty. In detail, the [Ownership concept](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) makes resource management straightforward, and **data-race-free concurrency** strengthens security even in multi-threaded environments. It offers consistent module management aligned with Python or JPMS (Java Platform Module System), easy encapsulation, and other flexible language characteristics. The ability to connect seamlessly with Java via FFI (Foreign Function Interface) is particularly compelling. -> You can check the history from the first public version to this version in [this document](AGES_EN.md). +All security operations of EntanglementLib are executed in this native layer. Specifically, it provides the following capabilities: -# Inspiration and Contribution +- [X] Hardware true random, mixed random, and quantum random number (Quantum RNG) generation +- [X] AEAD encryption (ChaCha20) +- [X] Secure Buffer that guarantees memory erasure +- [X] Constant-Time operations +- [X] Hash functions (including SHA2, SHA3, SHAKE) -Just in time, the respected security organization `Legion of the BouncyCastle Inc` started developing [`bc-rust`](https://github.com/bcgit/bc-rust/), and I got a lot of inspiration that would be useful for the EntanglementLib bridging technology. They have always been my strength from when I started developing the EntanglementLib until now. Anyway, I will maintain this development speed and continuously modify this document according to future updates. Eventually, I plan to continue developing towards this goal. +Each feature is managed as a separate crate. The root uses a virtual manifest, making sub-crate management straightforward. In addition, a dedicated crate implements the FFI functions to fundamentally block incorrect calls when the native is used from the Java side. This crate serves to expose only the essential functions that the Java side should call. -> [!TIP] -> Your feedback is always a great strength. If you wish to contribute to this project, please refer to [here](CONTRIBUTION_EN.md)! +This native library uses **no external dependencies (crates)** for the implementation of its core security features. In other words, it fundamentally does not trust any resources imported from outside. This development philosophy fully supports the **Zero Trust principle**, and the resulting single artifact (the EntanglementLib) operates smoothly even in closed environments. This perfectly aligns with the **Air-Gapped Ready** principle. + +Ultimately, this native is a precious resource cultivated in a strict environment and is actively and safely utilized throughout the EntanglementLib. + +## Future Plans + +This native still has a long way to go. We must implement a wide variety of classical encryption algorithm modules. + +- [ ] AES (128, 192, 256) +- [ ] ARIA (128, 192, 256) +- [ ] RSA (2048, 4096, 8192) +- [ ] ED25519, ED448 signatures +- [ ] X25519, X448 key agreement + +In addition, essential cryptographic primitives such as HMAC and HKDF must be provided. + +The Post-Quantum Cryptography (PQC) algorithms have the following targets: + +- [ ] [FIPS 203 (Module Lattice-based Key Encapsulation Mechanism, ML-KEM)](https://csrc.nist.gov/pubs/fips/203/final) +- [ ] [FIPS 204 (Module Lattice-based Digital Signature Algorithm, ML-DSA)](https://csrc.nist.gov/pubs/fips/204/final) +- [ ] [FIPS 205 (Stateless Hash-based Digital Signature Algorithm, SLH-DSA)](https://csrc.nist.gov/pubs/fips/205/final) + +Once the above PQC algorithms are implemented, the following TLS features must also be provided: -# Alpha Version +- [ ] TLS 1.3 +- [ ] X25519MLKEM768 according to [`draft-ietf-tls-ecdhe-mlkem`](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/) -This native library will not be released immediately even if preparations for the `1.1.x` release are completed. Therefore, to review the code in detail and precisely, and to smoothly build that environment, I will first release it as an alpha version at this point. +PKIX, JWT and CWT, OTP, and many others — there is clearly still a long road ahead. + +## Certification and Compliance Requirements + +Implementation alone is not enough. Every feature implemented in this native must completely follow the security implementation (specification) requirements set by international certification authorities and must obtain formal certification. Until then, no algorithm is considered “safe.” Hidden variables can surface at any time. + +Therefore, when using any functionality from this native, please treat it as an **experimental** feature or use it with that understanding. + +> [!NOTE] +> Features that have passed strict certification and regulatory review will be updated immediately. You can check the relevant information in [this document](COMPLIANCE_EN.md). + +# Inspiration and Contribution + +Coincidentally, the respected security collective `Legion of the BouncyCastle Inc` has begun development of [`bc-rust`](https://github.com/bcgit/bc-rust/), providing a great deal of inspiration that is highly relevant to EntanglementLib’s bridging technology. They have been a constant source of strength for me from the very beginning of EntanglementLib development up to now. In any case, I will maintain this development pace and will continue to update this document in line with future releases. Ultimately, development will proceed steadily toward this goal. + +> [!TIP] +> Your feedback is always an enormous help. If you would like to contribute to this project, please refer to [this guide](CONTRIBUTION_EN.md)! # Benchmarking -Benchmarking of this native library is conducted through the `criterion` crate. Detailed results of each benchmark can be found under the [benchmarks directory](benchmarks). \ No newline at end of file +Benchmarking of this native library is performed using the `criterion` crate. Detailed results for each benchmark can be found in the [benchmarks subdirectory](benchmarks). \ No newline at end of file From 6651ad2e4d0df7b08408c1889cc983cdee9a9ab7 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Fri, 27 Feb 2026 00:01:56 +0900 Subject: [PATCH 4/7] =?UTF-8?q?ChaCha20=20(AEAD)=20=EC=95=94=ED=98=B8?= =?UTF-8?q?=ED=99=94=20=EC=95=8C=EA=B3=A0=EB=A6=AC=EC=A6=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B2=A4=EC=B9=98?= =?UTF-8?q?=EB=A7=88=ED=82=B9,=20FFI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crypto/chacha20/Cargo.toml | 17 ++ crypto/chacha20/benches/chacha20_bench.rs | 118 ++++++++++ crypto/chacha20/src/chacha20.rs | 183 ++++++++++++++++ crypto/chacha20/src/chacha20_state.rs | 183 ++++++++++++++++ crypto/chacha20/src/lib.rs | 3 + crypto/chacha20/src/poly1305.rs | 248 ++++++++++++++++++++++ crypto/chacha20/tests/chacha20_test.rs | 155 ++++++++++++++ internal/ffi/src/chacha20_ffi.rs | 122 +++++++++++ 8 files changed, 1029 insertions(+) create mode 100644 crypto/chacha20/Cargo.toml create mode 100644 crypto/chacha20/benches/chacha20_bench.rs create mode 100644 crypto/chacha20/src/chacha20.rs create mode 100644 crypto/chacha20/src/chacha20_state.rs create mode 100644 crypto/chacha20/src/lib.rs create mode 100644 crypto/chacha20/src/poly1305.rs create mode 100644 crypto/chacha20/tests/chacha20_test.rs create mode 100644 internal/ffi/src/chacha20_ffi.rs diff --git a/crypto/chacha20/Cargo.toml b/crypto/chacha20/Cargo.toml new file mode 100644 index 0000000..72e58a7 --- /dev/null +++ b/crypto/chacha20/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "entlib-native-chacha20" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +entlib-native-constant-time.workspace = true +entlib-native-core-secure.workspace = true + +[dev-dependencies] +criterion = { version = "0.8.2", features = ["html_reports"] } + +[[bench]] +name = "chacha20_bench" +harness = false \ No newline at end of file diff --git a/crypto/chacha20/benches/chacha20_bench.rs b/crypto/chacha20/benches/chacha20_bench.rs new file mode 100644 index 0000000..80779d0 --- /dev/null +++ b/crypto/chacha20/benches/chacha20_bench.rs @@ -0,0 +1,118 @@ +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; + +// 프로젝트 구조에 맞게 경로를 수정하여 사용하십시오. +use entlib_native_chacha20::chacha20::{chacha20_poly1305_decrypt, chacha20_poly1305_encrypt}; +use entlib_native_chacha20::chacha20_state::process_chacha20; + +/// 순수 ChaCha20 블록 암호화 처리량 (Throughput) 벤치마크 +/// 다양한 페이로드 크기(64B ~ 64KB)에 대한 스트림 암호화 성능을 측정합니다. +fn bench_chacha20_pure(c: &mut Criterion) { + let mut group = c.benchmark_group("ChaCha20_Pure_Throughput"); + let key = [0u8; 32]; + let nonce = [0u8; 12]; + let counter = 1; + + for size in [64, 1024, 8192, 65536].iter() { + group.throughput(Throughput::Bytes(*size as u64)); + group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &s| { + let data = vec![0u8; s]; + b.iter(|| { + // black_box를 통해 컴파일러의 Dead Code Elimination 최적화 방지 + let _ = process_chacha20( + black_box(&key), + black_box(&nonce), + black_box(counter), + black_box(&data), + ); + }); + }); + } + group.finish(); +} + +/// ChaCha20-Poly1305 AEAD 암호화 처리량 벤치마크 +/// MAC 계산 및 메모리 복사 오버헤드가 포함된 실제 프로토콜 레벨의 암호화 성능을 측정합니다. +fn bench_aead_encrypt(c: &mut Criterion) { + let mut group = c.benchmark_group("ChaCha20_Poly1305_Encrypt"); + let key = [0u8; 32]; + let nonce = [0u8; 12]; + let aad = [0u8; 16]; // 일반적인 프로토콜 헤더 사이즈 가정 + + for size in [64, 1024, 8192, 65536].iter() { + group.throughput(Throughput::Bytes(*size as u64)); + group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &s| { + let pt = vec![0u8; s]; + b.iter(|| { + let _ = chacha20_poly1305_encrypt( + black_box(&key), + black_box(&nonce), + black_box(&aad), + black_box(&pt), + ); + }); + }); + } + group.finish(); +} + +/// 보안성 검증: 상수-시간(Constant-Time) 복호화 타이밍 벤치마크 +/// 가장 중요한 보안 벤치마크입니다. 조작된 MAC이 입력되었을 때 Early-Return 하지 않고 +/// 항상 동일한 복호화 연산을 수행하는지(Zero-Trust, 분기 완전 제거) 측정합니다. +/// 두 함수의 실행 시간 그래프가 완전히 일치(Overlapping)해야 안전한 구현입니다. +fn bench_aead_decrypt_constant_time(c: &mut Criterion) { + let mut group = c.benchmark_group("ChaCha20_Poly1305_Decrypt_Security_Test"); + let key = [0u8; 32]; + let nonce = [0u8; 12]; + let aad = [0u8; 16]; + + // 8KB 페이로드 기준 테스트 + let pt_size = 8192; + let pt = vec![0u8; pt_size]; + + // 정상적인 암호문 및 태그 생성 + let valid_ct_buf = chacha20_poly1305_encrypt(&key, &nonce, &aad, &pt); + let valid_ct = valid_ct_buf.inner.clone(); + + // 조작된 태그를 가진 암호문 (마지막 바이트 1비트 반전) + let mut invalid_ct = valid_ct.clone(); + let last_idx = invalid_ct.len() - 1; + invalid_ct[last_idx] ^= 1; + + group.throughput(Throughput::Bytes(pt_size as u64)); + + // Case 1: 인증에 성공하는 정상 트래픽 + group.bench_function("Valid_MAC_Traffic", |b| { + b.iter(|| { + let _ = chacha20_poly1305_decrypt( + black_box(&key), + black_box(&nonce), + black_box(&aad), + black_box(&valid_ct), + ); + }); + }); + + // Case 2: 인증에 실패하는 공격 트래픽 + // 정상 트래픽과 처리 시간/CPU 사이클이 동일해야 타이밍 공격에 안전합니다. + group.bench_function("Invalid_MAC_Attack_Traffic", |b| { + b.iter(|| { + let _ = chacha20_poly1305_decrypt( + black_box(&key), + black_box(&nonce), + black_box(&aad), + black_box(&invalid_ct), + ); + }); + }); + + group.finish(); +} + +criterion_group!( + benches, + bench_chacha20_pure, + bench_aead_encrypt, + bench_aead_decrypt_constant_time +); +criterion_main!(benches); diff --git a/crypto/chacha20/src/chacha20.rs b/crypto/chacha20/src/chacha20.rs new file mode 100644 index 0000000..2b4d191 --- /dev/null +++ b/crypto/chacha20/src/chacha20.rs @@ -0,0 +1,183 @@ +//! CHaCha20 (poly1305) 암호화 모듈입니다. +//! +//! # Authors +//! Q. T. Felix + +use crate::chacha20_state::process_chacha20; +use crate::poly1305::generate_poly1305; +use core::ptr::write_volatile; +use core::sync::atomic::{Ordering, compiler_fence}; +use entlib_native_constant_time::constant_time::ConstantTimeOps; +use entlib_native_core_secure::secure_buffer::SecureBuffer; + +const MAX_MAC_BUFFER_LEN: usize = 2048; // todo: 프로토콜 최대 패킷(MTU) 사양에 맞게 조정, def2048 + +#[inline(always)] +fn append_padded(buffer: &mut [u8], offset: &mut usize, data: &[u8]) { + let len = data.len(); + buffer[*offset..*offset + len].copy_from_slice(data); + *offset += len; + + let rem = *offset % 16; + if rem != 0 { + *offset += 16 - rem; + } +} + +/// RFC 8439 ChaCha20-Poly1305 AEAD 암호화 함수입니다. +/// +/// # Security +/// - mac_data Vec 재할당 완전 차단 (정확한 최대 크기 사전 capacity) +/// - 모든 민감 데이터 사용 즉시 zeroize +/// +/// # Return +/// `ciphertext || tag` 형태의 `SecureBuffer` +pub fn chacha20_poly1305_encrypt( + key: &[u8; 32], + nonce: &[u8; 12], + aad: &[u8], + plaintext: &[u8], +) -> SecureBuffer { + // 1. Poly1305 One-Time Key (counter = 0) + let otk_secure = process_chacha20(key, nonce, 0, &[0u8; 32]); + let mut otk = [0u8; 32]; + otk.copy_from_slice(&otk_secure.inner[0..32]); + + // 2. Ciphertext (counter = 1) + let ct_secure = process_chacha20(key, nonce, 1, plaintext); + + // 3. Poly1305 MAC 입력 구성 (고정 크기 스택 배열 사용) + let max_mac_len = aad.len() + ct_secure.inner.len() + 48; + assert!( + max_mac_len <= MAX_MAC_BUFFER_LEN, + "MAC data exceeds max buffer length" + ); + + let mut mac_data = [0u8; MAX_MAC_BUFFER_LEN]; + let mut offset = 0; + + append_padded(&mut mac_data, &mut offset, aad); + append_padded(&mut mac_data, &mut offset, &ct_secure.inner); + + mac_data[offset..offset + 8].copy_from_slice(&(aad.len() as u64).to_le_bytes()); + offset += 8; + mac_data[offset..offset + 8].copy_from_slice(&(ct_secure.inner.len() as u64).to_le_bytes()); + offset += 8; + + // 4. Tag 생성 (전체 버퍼가 아닌 실제 데이터 길이만큼만 전달) + let tag_secure = generate_poly1305(&otk, &mac_data[..offset]); + + // 5. 최종 결과 = CT + Tag + let ct_len = ct_secure.inner.len(); + let mut result = SecureBuffer { + inner: vec![0u8; ct_len + 16], + }; + result.inner[0..ct_len].copy_from_slice(&ct_secure.inner); + result.inner[ct_len..].copy_from_slice(&tag_secure.inner); + + // 6. 모든 민감 데이터 즉시 zeroize + drop(otk_secure); + drop(ct_secure); + drop(tag_secure); + + for b in mac_data.iter_mut() { + unsafe { + write_volatile(b, 0); + } + } + for b in otk.iter_mut() { + unsafe { + write_volatile(b, 0); + } + } + compiler_fence(Ordering::SeqCst); + + result +} + +/// RFC 8439 ChaCha20-Poly1305 AEAD 복호화 함수입니다. +/// +/// # Security +/// - Verify-then-Decrypt 패턴 완벽 준수 (MAC 검증 -> 성공시에만 복호화) +/// - mac_data Vec 재할당 완전 차단 +/// - 검증 실패 시 `None` 반환 -> 0-byte 평문 충돌 방지 및 명확한 에러 핸들링 보장 +pub fn chacha20_poly1305_decrypt( + key: &[u8; 32], + nonce: &[u8; 12], + aad: &[u8], + ciphertext_with_tag: &[u8], +) -> Option { + // 0. 기본 길이 검증 (공개 정보) + if ciphertext_with_tag.len() < 16 { + return None; + } + + let ct_len = ciphertext_with_tag.len() - 16; + let ct = &ciphertext_with_tag[0..ct_len]; + let received_tag = &ciphertext_with_tag[ct_len..]; + + // 1. Poly1305 One-Time Key 생성 + let otk_secure = process_chacha20(key, nonce, 0, &[0u8; 32]); + let mut otk = [0u8; 32]; + otk.copy_from_slice(&otk_secure.inner[0..32]); + + // 2. MAC 입력 데이터 구성 (반드시 고정 크기 스택 배열 사용) + let max_mac_len = aad.len() + ct.len() + 48; + if max_mac_len > MAX_MAC_BUFFER_LEN { + return None; // 스펙 초과 시 즉시 실패 + } + + let mut mac_data = [0u8; MAX_MAC_BUFFER_LEN]; + let mut offset = 0; + + append_padded(&mut mac_data, &mut offset, aad); + append_padded(&mut mac_data, &mut offset, ct); + + mac_data[offset..offset + 8].copy_from_slice(&(aad.len() as u64).to_le_bytes()); + offset += 8; + mac_data[offset..offset + 8].copy_from_slice(&(ct.len() as u64).to_le_bytes()); + offset += 8; + + // 3. Expected Tag 계산 + let expected_tag_secure = generate_poly1305(&otk, &mac_data[..offset]); + + // 4. 상수-시간 tag 검증 + let mut xor_diff = 0u8; + for i in 0..16 { + xor_diff |= expected_tag_secure.inner[i] ^ received_tag[i]; + } + let mask = xor_diff.ct_is_zero(); // valid -> 0xFF, invalid -> 0x00 + + // 5. 조건 분기 없는 완전한 항상 복호화 수행 및 ct_select 선택 + let pt_secure = process_chacha20(key, nonce, 1, ct); + let mut result_buf = SecureBuffer { + inner: vec![0u8; ct_len], + }; + + // 마스크에 따라 원본 복호화 데이터(0xFF) 또는 0(0x00)을 상수-시간으로 선택 + for i in 0..ct_len { + result_buf.inner[i] = pt_secure.inner[i].ct_select(0, mask); + } + drop(pt_secure); + + // 6. 민감 데이터 즉시 소거 (스택 배열은 사용된 길이까지만 소거) + drop(otk_secure); + drop(expected_tag_secure); + + for byte in mac_data.iter_mut().take(offset) { + unsafe { + write_volatile(byte, 0); + } + } + for b in otk.iter_mut() { + unsafe { + write_volatile(b, 0); + } + } + compiler_fence(Ordering::SeqCst); + + // 7. 결과 반환 + // Q. T. Felix NOTE: 여기서 논리적 수행을 위한 분기를 사용하는데 암호화된 값 반환에 대한 조건문이라서 + // 크게 상관없을거라 판단했고 일단 놔두겠지만, 추 후 상수-시간 연산 도입 고려해볼 만 함 + if mask == 0xFF { Some(result_buf) } else { None } +} diff --git a/crypto/chacha20/src/chacha20_state.rs b/crypto/chacha20/src/chacha20_state.rs new file mode 100644 index 0000000..4341932 --- /dev/null +++ b/crypto/chacha20/src/chacha20_state.rs @@ -0,0 +1,183 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{Ordering, compiler_fence}; +use entlib_native_constant_time::constant_time::ConstantTimeOps; +use entlib_native_core_secure::secure_buffer::SecureBuffer; + +/// 내부 연산을 위해 캡슐화된 ChaCha20 상태(state) 구조체입니다. +/// 메모리 안전성을 위해 스코프를 벗어날 때 즉각적으로 데이터를 소거(zeroize)합니다. +struct ChaCha20State { + inner: [u32; 16], +} + +impl ChaCha20State { + #[inline(always)] + fn new(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> Self { + let mut state = [0u32; 16]; + + // 상수(constants) "expand 32-byte k" + state[0] = 0x61707865; + state[1] = 0x3320646e; + state[2] = 0x79622d32; + state[3] = 0x6b206574; + + // 키(key) + for i in 0..8 { + state[4 + i] = u32::from_le_bytes(key[i * 4..(i + 1) * 4].try_into().unwrap()); + } + + // 카운터(counter) + state[12] = counter; + + // 논스(nonce) + for i in 0..3 { + state[13 + i] = u32::from_le_bytes(nonce[i * 4..(i + 1) * 4].try_into().unwrap()); + } + + Self { inner: state } + } + + #[inline(always)] + fn process_block(&mut self, output: &mut [u8; 64]) { + let mut working_state = self.inner; + + for _ in 0..10 { + // 열 라운드(column round) + self.quarter_round_on_state(&mut working_state, 0, 4, 8, 12); + self.quarter_round_on_state(&mut working_state, 1, 5, 9, 13); + self.quarter_round_on_state(&mut working_state, 2, 6, 10, 14); + self.quarter_round_on_state(&mut working_state, 3, 7, 11, 15); + + // 대각선 라운드(diagonal round) + self.quarter_round_on_state(&mut working_state, 0, 5, 10, 15); + self.quarter_round_on_state(&mut working_state, 1, 6, 11, 12); + self.quarter_round_on_state(&mut working_state, 2, 7, 8, 13); + self.quarter_round_on_state(&mut working_state, 3, 4, 9, 14); + } + + for i in 0..16 { + working_state[i] = working_state[i].wrapping_add(self.inner[i]); + let bytes = working_state[i].to_le_bytes(); + output[i * 4..(i + 1) * 4].copy_from_slice(&bytes); + } + + // 블록 처리 완료 후 워킹 상태 명시적 소거(zeroize) + for word in working_state.iter_mut() { + unsafe { + write_volatile(word, 0); + } + } + compiler_fence(Ordering::SeqCst); + } + + #[inline(always)] + fn quarter_round_on_state( + &self, + state: &mut [u32; 16], + a: usize, + b: usize, + c: usize, + d: usize, + ) { + state[a] = state[a].wrapping_add(state[b]); + state[d] ^= state[a]; + state[d] = state[d].rotate_left(16); + state[c] = state[c].wrapping_add(state[d]); + state[b] ^= state[c]; + state[b] = state[b].rotate_left(12); + state[a] = state[a].wrapping_add(state[b]); + state[d] ^= state[a]; + state[d] = state[d].rotate_left(8); + state[c] = state[c].wrapping_add(state[d]); + state[b] ^= state[c]; + state[b] = state[b].rotate_left(7); + } + + #[inline(always)] + fn increment_counter(&mut self) { + self.inner[12] = self.inner[12].wrapping_add(1); + } +} + +impl Drop for ChaCha20State { + fn drop(&mut self) { + for word in self.inner.iter_mut() { + unsafe { + write_volatile(word, 0); + } + } + compiler_fence(Ordering::SeqCst); + } +} + +/// ChaCha20 알고리즘을 사용하여 데이터를 암호화 또는 복호화합니다. +/// 연산 결과는 자바(java) 힙의 생명주기와 분리된 `SecureBuffer`로 반환됩니다. +pub fn process_chacha20( + key: &[u8; 32], + nonce: &[u8; 12], + initial_counter: u32, + data: &[u8], +) -> SecureBuffer { + let mut state = ChaCha20State::new(key, nonce, initial_counter); + let mut result = SecureBuffer { + inner: vec![0u8; data.len()], + }; + + let mut key_stream_block = [0u8; 64]; + let mut chunks = data.chunks_exact(64); + let mut out_chunks = result.inner.chunks_exact_mut(64); + + // 전체 블록(full block) 처리 + for (chunk, out_chunk) in chunks.by_ref().zip(out_chunks.by_ref()) { + state.process_block(&mut key_stream_block); + for i in 0..64 { + out_chunk[i] = chunk[i] ^ key_stream_block[i]; + } + state.increment_counter(); + } + + // 잔여 블록(partial block) 처리 + let remainder = chunks.remainder(); + if !remainder.is_empty() { + let out_remainder = out_chunks.into_remainder(); + let rem_len = remainder.len(); + state.process_block(&mut key_stream_block); + + // 메모리 경계(out-of-bounds) 오류를 방지하기 위해 64바이트 임시 버퍼 사용 + let mut temp_block = [0u8; 64]; + + temp_block[..rem_len].copy_from_slice(&remainder[..rem_len]); + + // 전체 블록 길이에 대해 고정 반복하여 암호화 핵심 연산의 타이밍 리스크 제거 + for i in 0..64 { + let xored = temp_block[i] ^ key_stream_block[i]; + + // 인덱스 `i`가 잔여 블록 길이(`rem_len`) 미만인지 판별하는 상수-시간 마스크(mask) 생성 + // `i - rem_len`이 음수이면 참(All 1s), 0 이상이면 거짓(0) 반환 + let diff = (i as isize).wrapping_sub(rem_len as isize); + let mask = diff.ct_is_negative() as u8; + + // 마스크에 따라 XOR된 값 또는 원본 임시 버퍼 값 선택 (CT MUX) + temp_block[i] = xored.ct_select(temp_block[i], mask); + } + + // 마스킹이 완료된 안전한 데이터를 결과 버퍼에 복사 + out_remainder[..rem_len].copy_from_slice(&temp_block[..rem_len]); + + // 임시 버퍼 안전 소거(zeroize) + for byte in temp_block.iter_mut() { + unsafe { + write_volatile(byte, 0); + } + } + } + + // 키 스트림 블록 안전 소거 + for byte in key_stream_block.iter_mut() { + unsafe { + write_volatile(byte, 0); + } + } + compiler_fence(Ordering::SeqCst); + + result +} diff --git a/crypto/chacha20/src/lib.rs b/crypto/chacha20/src/lib.rs new file mode 100644 index 0000000..947c845 --- /dev/null +++ b/crypto/chacha20/src/lib.rs @@ -0,0 +1,3 @@ +pub mod chacha20; +pub mod chacha20_state; +pub mod poly1305; diff --git a/crypto/chacha20/src/poly1305.rs b/crypto/chacha20/src/poly1305.rs new file mode 100644 index 0000000..69c9ad1 --- /dev/null +++ b/crypto/chacha20/src/poly1305.rs @@ -0,0 +1,248 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{Ordering, compiler_fence}; +use entlib_native_constant_time::constant_time::ConstantTimeOps; +use entlib_native_core_secure::secure_buffer::SecureBuffer; + +/// 내부 연산을 위해 캡슐화된 Poly1305 상태(state) 구조체입니다. +/// 메모리 안전성을 위해 스코프를 벗어날 때 즉각적으로 데이터를 소거(zeroize)합니다. +struct Poly1305State { + r: [u32; 5], + h: [u32; 5], + pad: [u32; 4], +} + +impl Poly1305State { + #[inline(always)] + fn new(key: &[u8; 32]) -> Self { + // 상수-시간 클램핑(clamping) 적용 + let t0 = u32::from_le_bytes(key[0..4].try_into().unwrap()); + let t1 = u32::from_le_bytes(key[4..8].try_into().unwrap()); + let t2 = u32::from_le_bytes(key[8..12].try_into().unwrap()); + let t3 = u32::from_le_bytes(key[12..16].try_into().unwrap()); + + let r0 = t0 & 0x03ffffff; + let r1 = ((t0 >> 26) | (t1 << 6)) & 0x03ffff03; + let r2 = ((t1 >> 20) | (t2 << 12)) & 0x03ffc0ff; + let r3 = ((t2 >> 14) | (t3 << 18)) & 0x03f03fff; + let r4 = (t3 >> 8) & 0x000fffff; + + let pad0 = u32::from_le_bytes(key[16..20].try_into().unwrap()); + let pad1 = u32::from_le_bytes(key[20..24].try_into().unwrap()); + let pad2 = u32::from_le_bytes(key[24..28].try_into().unwrap()); + let pad3 = u32::from_le_bytes(key[28..32].try_into().unwrap()); + + Self { + r: [r0, r1, r2, r3, r4], + h: [0; 5], + pad: [pad0, pad1, pad2, pad3], + } + } + + #[inline(always)] + fn process_block(&mut self, block: &[u8; 16], is_full: u8) { + let m0 = u32::from_le_bytes(block[0..4].try_into().unwrap()); + let m1 = u32::from_le_bytes(block[4..8].try_into().unwrap()); + let m2 = u32::from_le_bytes(block[8..12].try_into().unwrap()); + let m3 = u32::from_le_bytes(block[12..16].try_into().unwrap()); + + // Radix-26 분할 및 129번째 비트(패딩) 병합 + let limb0 = m0 & 0x03ffffff; + let limb1 = ((m0 >> 26) | (m1 << 6)) & 0x03ffffff; + let limb2 = ((m1 >> 20) | (m2 << 12)) & 0x03ffffff; + let limb3 = ((m2 >> 14) | (m3 << 18)) & 0x03ffffff; + let mut limb4 = m3 >> 8; + + // 전체 블록일 경우에만 129번째 비트를 추가 (CT MUX 사용) + let pad_bit = 1u32 << 24; + let mask = (is_full as u32).wrapping_neg(); // 1 -> 0xFFFFFFFF, 0 -> 0x00000000 + limb4 |= pad_bit.ct_select(0, mask); + + self.h[0] += limb0; + self.h[1] += limb1; + self.h[2] += limb2; + self.h[3] += limb3; + self.h[4] += limb4; + + let s1 = self.r[1] * 5; + let s2 = self.r[2] * 5; + let s3 = self.r[3] * 5; + let s4 = self.r[4] * 5; + + // u64 캐스팅을 통한 오버플로우 방지 모듈러 곱셈 + let d0 = (self.h[0] as u64 * self.r[0] as u64) + + (self.h[1] as u64 * s4 as u64) + + (self.h[2] as u64 * s3 as u64) + + (self.h[3] as u64 * s2 as u64) + + (self.h[4] as u64 * s1 as u64); + let d1 = (self.h[0] as u64 * self.r[1] as u64) + + (self.h[1] as u64 * self.r[0] as u64) + + (self.h[2] as u64 * s4 as u64) + + (self.h[3] as u64 * s3 as u64) + + (self.h[4] as u64 * s2 as u64); + let d2 = (self.h[0] as u64 * self.r[2] as u64) + + (self.h[1] as u64 * self.r[1] as u64) + + (self.h[2] as u64 * self.r[0] as u64) + + (self.h[3] as u64 * s4 as u64) + + (self.h[4] as u64 * s3 as u64); + let d3 = (self.h[0] as u64 * self.r[3] as u64) + + (self.h[1] as u64 * self.r[2] as u64) + + (self.h[2] as u64 * self.r[1] as u64) + + (self.h[3] as u64 * self.r[0] as u64) + + (self.h[4] as u64 * s4 as u64); + let d4 = (self.h[0] as u64 * self.r[4] as u64) + + (self.h[1] as u64 * self.r[3] as u64) + + (self.h[2] as u64 * self.r[2] as u64) + + (self.h[3] as u64 * self.r[1] as u64) + + (self.h[4] as u64 * self.r[0] as u64); + + // 캐리(carry) 전파 + let mut c = (d0 >> 26) as u32; + self.h[0] = (d0 as u32) & 0x03ffffff; + let d1_v = d1 + c as u64; + c = (d1_v >> 26) as u32; + self.h[1] = (d1_v as u32) & 0x03ffffff; + let d2_v = d2 + c as u64; + c = (d2_v >> 26) as u32; + self.h[2] = (d2_v as u32) & 0x03ffffff; + let d3_v = d3 + c as u64; + c = (d3_v >> 26) as u32; + self.h[3] = (d3_v as u32) & 0x03ffffff; + let d4_v = d4 + c as u64; + c = (d4_v >> 26) as u32; + self.h[4] = (d4_v as u32) & 0x03ffffff; + self.h[0] += c * 5; + c = self.h[0] >> 26; + self.h[0] &= 0x03ffffff; + self.h[1] += c; + } + + #[inline(always)] + fn finalize(mut self, output: &mut [u8; 16]) { + // 잔여 캐리 완벽 전파 + let mut c = self.h[1] >> 26; + self.h[1] &= 0x03ffffff; + self.h[2] += c; + c = self.h[2] >> 26; + self.h[2] &= 0x03ffffff; + self.h[3] += c; + c = self.h[3] >> 26; + self.h[3] &= 0x03ffffff; + self.h[4] += c; + c = self.h[4] >> 26; + self.h[4] &= 0x03ffffff; + self.h[0] += c * 5; + c = self.h[0] >> 26; + self.h[0] &= 0x03ffffff; + self.h[1] += c; + + // h + 5 모듈러 감산을 위한 임시 값 계산 + let mut g0 = self.h[0] + 5; + c = g0 >> 26; + g0 &= 0x03ffffff; + let mut g1 = self.h[1] + c; + c = g1 >> 26; + g1 &= 0x03ffffff; + let mut g2 = self.h[2] + c; + c = g2 >> 26; + g2 &= 0x03ffffff; + let mut g3 = self.h[3] + c; + c = g3 >> 26; + g3 &= 0x03ffffff; + let mut g4 = self.h[4] + c; + + // g4에서 2^130 (1 << 26)을 뺐을 때 음수인지 판별 + let sub = g4.wrapping_sub(1 << 26); + let mask = sub.ct_is_negative(); + + // 뺄셈 결과가 음수면 원래의 h를, 아니면 g를 선택 + g4 = sub.ct_select(g4, mask) & 0x03ffffff; + let f0 = self.h[0].ct_select(g0, mask); + let f1 = self.h[1].ct_select(g1, mask); + let f2 = self.h[2].ct_select(g2, mask); + let f3 = self.h[3].ct_select(g3, mask); + let f4 = self.h[4].ct_select(g4, mask); + + // 32비트 결합 + let mut out0 = f0 | (f1 << 26); + let mut out1 = (f1 >> 6) | (f2 << 20); + let mut out2 = (f2 >> 12) | (f3 << 14); + let mut out3 = (f3 >> 18) | (f4 << 8); + + // 패딩(pad) 값 상수-시간 병합 + let (v0, c0) = out0.overflowing_add(self.pad[0]); + out0 = v0; + let (v1, c1) = out1.overflowing_add(self.pad[1].wrapping_add(c0 as u32)); + out1 = v1; + let (v2, c2) = out2.overflowing_add(self.pad[2].wrapping_add(c1 as u32)); + out2 = v2; + let (v3, _) = out3.overflowing_add(self.pad[3].wrapping_add(c2 as u32)); + out3 = v3; + + output[0..4].copy_from_slice(&out0.to_le_bytes()); + output[4..8].copy_from_slice(&out1.to_le_bytes()); + output[8..12].copy_from_slice(&out2.to_le_bytes()); + output[12..16].copy_from_slice(&out3.to_le_bytes()); + } +} + +impl Drop for Poly1305State { + fn drop(&mut self) { + for word in self + .r + .iter_mut() + .chain(self.h.iter_mut()) + .chain(self.pad.iter_mut()) + { + unsafe { + write_volatile(word, 0); + } + } + compiler_fence(Ordering::SeqCst); + } +} + +/// Poly1305 MAC을 생성합니다. +/// 연산 결과는 Java 힙의 생명주기와 분리된 `SecureBuffer`로 반환됩니다. +pub fn generate_poly1305(key: &[u8; 32], data: &[u8]) -> SecureBuffer { + let mut state = Poly1305State::new(key); + let mut chunks = data.chunks_exact(16); + + for chunk in chunks.by_ref() { + let block: &[u8; 16] = chunk.try_into().unwrap(); + state.process_block(block, 1); + } + + let remainder = chunks.remainder(); + if !remainder.is_empty() { + let mut pad_block = [0u8; 16]; + let rem_len = remainder.len(); + + pad_block[..rem_len].copy_from_slice(&remainder[..rem_len]); + pad_block[rem_len] = 1; // RFC 8439 잔여 블록 패딩 적용 + + state.process_block(&pad_block, 0); + + for byte in pad_block.iter_mut() { + unsafe { + write_volatile(byte, 0); + } + } + } + + let mut mac = [0u8; 16]; + state.finalize(&mut mac); + + let mut result = SecureBuffer { + inner: vec![0u8; 16], + }; + result.inner.copy_from_slice(&mac); + + for byte in mac.iter_mut() { + unsafe { + write_volatile(byte, 0); + } + } + compiler_fence(Ordering::SeqCst); + + result +} diff --git a/crypto/chacha20/tests/chacha20_test.rs b/crypto/chacha20/tests/chacha20_test.rs new file mode 100644 index 0000000..f9d4fda --- /dev/null +++ b/crypto/chacha20/tests/chacha20_test.rs @@ -0,0 +1,155 @@ +#[cfg(test)] +mod tests { + use entlib_native_chacha20::chacha20::{chacha20_poly1305_decrypt, chacha20_poly1305_encrypt}; + + /// RFC 8439 Section 2.8.2. ChaCha20-Poly1305 Test Vector + #[test] + fn test_rfc8439_aead_vector() { + let key: [u8; 32] = [ + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, + ]; + let nonce: [u8; 12] = [ + 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + ]; + let aad: [u8; 12] = [ + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + ]; + let plaintext = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + + let expected_ciphertext: [u8; 114] = [ + 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, + 0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, + 0x36, 0xee, 0x62, 0xd6, 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, + 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, + 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, + 0x8b, 0x8c, 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, 0xfa, 0xb3, 0x24, 0xe4, + 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, 0x3f, 0xf4, + 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, + 0x61, 0x16, + ]; + let expected_tag: [u8; 16] = [ + 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, + 0x06, 0x91, + ]; + + // 암호화 검증 + let ct_secure = chacha20_poly1305_encrypt(&key, &nonce, &aad, plaintext); + let ct_len = ct_secure.inner.len(); + + assert_eq!( + ct_len, + expected_ciphertext.len() + 16, + "Ciphertext length mismatch" + ); + assert_eq!( + &ct_secure.inner[..ct_len - 16], + &expected_ciphertext[..], + "Ciphertext data mismatch" + ); + assert_eq!( + &ct_secure.inner[ct_len - 16..], + &expected_tag[..], + "MAC tag mismatch" + ); + + // 복호화 검증 + let pt_secure_opt = chacha20_poly1305_decrypt(&key, &nonce, &aad, &ct_secure.inner); + assert!( + pt_secure_opt.is_some(), + "Decryption failed for valid RFC 8439 vector" + ); + + let pt_secure = pt_secure_opt.unwrap(); + assert_eq!( + &pt_secure.inner[..], + plaintext, + "Decrypted plaintext mismatch" + ); + } + + /// 조작된 암호문(Ciphertext)이 입력될 경우 Fail-fast (None 반환) 처리되는지 검증합니다. + #[test] + fn test_aead_tampered_ciphertext() { + let key = [0xAA; 32]; + let nonce = [0xBB; 12]; + let aad = b"header_data"; + let plaintext = b"secret_message"; + + let valid_ct = chacha20_poly1305_encrypt(&key, &nonce, aad, plaintext); + + // 1바이트 변조 (가장 첫 번째 암호문 바이트) + let mut invalid_ct = valid_ct.inner.clone(); + invalid_ct[0] ^= 0x01; + + let result = chacha20_poly1305_decrypt(&key, &nonce, aad, &invalid_ct); + assert!( + result.is_none(), + "Tampered ciphertext should NOT decrypt successfully" + ); + } + + /// 조작된 연관 데이터(AAD)가 입력될 경우 Fail-fast (None 반환) 처리되는지 검증합니다. + #[test] + fn test_aead_tampered_aad() { + let key = [0x11; 32]; + let nonce = [0x22; 12]; + let aad = b"protocol_v1"; + let plaintext = b"login_request"; + + let valid_ct = chacha20_poly1305_encrypt(&key, &nonce, aad, plaintext); + + // AAD 변조 (버전 정보 조작 시도) + let tampered_aad = b"protocol_v2"; + + let result = chacha20_poly1305_decrypt(&key, &nonce, tampered_aad, &valid_ct.inner); + assert!( + result.is_none(), + "Tampered AAD should NOT decrypt successfully" + ); + } + + /// 0-byte 평문 충돌 결함이 해결되었는지 검증합니다. + /// 빈 평문을 암호화하고 정상적으로 복호화되는지 확인합니다. + #[test] + fn test_aead_empty_plaintext() { + let key = [0x33; 32]; + let nonce = [0x44; 12]; + let aad = b"empty_payload_test"; + let plaintext: &[u8] = b""; + + let ct_secure = chacha20_poly1305_encrypt(&key, &nonce, aad, plaintext); + + // 암호문은 순수하게 16바이트의 MAC Tag로만 구성되어야 함 + assert_eq!(ct_secure.inner.len(), 16); + + let pt_secure_opt = chacha20_poly1305_decrypt(&key, &nonce, aad, &ct_secure.inner); + assert!( + pt_secure_opt.is_some(), + "0-byte plaintext should decrypt successfully" + ); + + let pt_secure = pt_secure_opt.unwrap(); + assert_eq!( + pt_secure.inner.len(), + 0, + "Decrypted plaintext should be empty" + ); + } + + /// 입력 길이가 16바이트(MAC 크기) 미만인 악의적인 패킷에 대한 검증 로직 테스트 + #[test] + fn test_aead_too_short_ciphertext() { + let key = [0x55; 32]; + let nonce = [0x66; 12]; + let aad = b""; + let short_ct = [0u8; 15]; // MAC 16바이트보다 짧은 데이터 + + let result = chacha20_poly1305_decrypt(&key, &nonce, aad, &short_ct); + assert!( + result.is_none(), + "Ciphertext shorter than 16 bytes must return None" + ); + } +} diff --git a/internal/ffi/src/chacha20_ffi.rs b/internal/ffi/src/chacha20_ffi.rs new file mode 100644 index 0000000..9054ef1 --- /dev/null +++ b/internal/ffi/src/chacha20_ffi.rs @@ -0,0 +1,122 @@ +//! ChaCha20-Poly1305 AEAD 및 저수준 모듈 FFI 인터페이스 (Java JNI / FFM API 연동용) +//! +//! # Security +//! - 모든 출력은 Opaque Pointer(*mut SecureBuffer) 형태로 반환 (Callee-allocated 패턴) +//! - Java 측에서 반드시 작업 완료 후 `free_secure_buffer`를 호출해야 함 (Zeroize + Dealloc 보장) +//! - decrypt 실패 시 null 포인터 반환 -> 명확한 AuthenticationFailedException 유도 +//! - 입력 길이 검증 필수, heap allocation 최소화, constant-time 보장 유지 + +use core::ptr::null_mut; +use entlib_native_core_secure::secure_buffer::SecureBuffer; + +use entlib_native_chacha20::chacha20::{chacha20_poly1305_decrypt, chacha20_poly1305_encrypt}; +use entlib_native_chacha20::chacha20_state::process_chacha20; +use entlib_native_chacha20::poly1305::generate_poly1305; + +/// ChaCha20 단일 처리 FFI (저수준 모듈 노출) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn process_chacha20_ffi( + key_ptr: *const u8, + key_len: usize, + nonce_ptr: *const u8, + nonce_len: usize, + counter: u32, + data_ptr: *const u8, + data_len: usize, +) -> *mut SecureBuffer { + if key_len != 32 || nonce_len != 12 { + return null_mut(); + } + + let key = unsafe { core::slice::from_raw_parts(key_ptr, 32) }; + let key_arr: &[u8; 32] = key.try_into().expect("key length already checked"); + let nonce = unsafe { core::slice::from_raw_parts(nonce_ptr, 12) }; + let nonce_arr: &[u8; 12] = nonce.try_into().expect("nonce length already checked"); + let data = unsafe { core::slice::from_raw_parts(data_ptr, data_len) }; + + let result = process_chacha20(key_arr, nonce_arr, counter, data); + + // SecureBuffer를 Box로 감싸 힙으로 이동시킨 후, 원시 포인터로 변환하여 Java로 전달 (Ownership transfer) + Box::into_raw(Box::new(result)) +} + +/// Poly1305 MAC 생성 FFI (저수준 모듈 노출) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn generate_poly1305_ffi( + key_ptr: *const u8, + key_len: usize, + data_ptr: *const u8, + data_len: usize, +) -> *mut SecureBuffer { + if key_len != 32 { + return null_mut(); + } + + let key = unsafe { core::slice::from_raw_parts(key_ptr, 32) }; + let key_arr: &[u8; 32] = key.try_into().expect("key length already checked"); + let data = unsafe { core::slice::from_raw_parts(data_ptr, data_len) }; + + let result = generate_poly1305(key_arr, data); + + Box::into_raw(Box::new(result)) +} + +/// RFC 8439 ChaCha20-Poly1305 AEAD 암호화 FFI +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chacha20_poly1305_encrypt_ffi( + key_ptr: *const u8, + key_len: usize, + nonce_ptr: *const u8, + nonce_len: usize, + aad_ptr: *const u8, + aad_len: usize, + plaintext_ptr: *const u8, + plaintext_len: usize, +) -> *mut SecureBuffer { + if key_len != 32 || nonce_len != 12 { + return null_mut(); + } + + let key = unsafe { core::slice::from_raw_parts(key_ptr, 32) }; + let key_arr: &[u8; 32] = key.try_into().expect("key length already checked"); + let nonce = unsafe { core::slice::from_raw_parts(nonce_ptr, 12) }; + let nonce_arr: &[u8; 12] = nonce.try_into().expect("nonce length already checked"); + let aad = unsafe { core::slice::from_raw_parts(aad_ptr, aad_len) }; + let plaintext = unsafe { core::slice::from_raw_parts(plaintext_ptr, plaintext_len) }; + + let result = chacha20_poly1305_encrypt(key_arr, nonce_arr, aad, plaintext); + + Box::into_raw(Box::new(result)) +} + +/// RFC 8439 ChaCha20-Poly1305 AEAD 복호화 FFI +/// 실패 시 null 반환 -> Java에서 AuthenticationFailedException 발생 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chacha20_poly1305_decrypt_ffi( + key_ptr: *const u8, + key_len: usize, + nonce_ptr: *const u8, + nonce_len: usize, + aad_ptr: *const u8, + aad_len: usize, + ciphertext_with_tag_ptr: *const u8, + ciphertext_with_tag_len: usize, +) -> *mut SecureBuffer { + if key_len != 32 || nonce_len != 12 { + return null_mut(); + } + + let key = unsafe { core::slice::from_raw_parts(key_ptr, 32) }; + let key_arr: &[u8; 32] = key.try_into().expect("key length already checked"); + let nonce = unsafe { core::slice::from_raw_parts(nonce_ptr, 12) }; + let nonce_arr: &[u8; 12] = nonce.try_into().expect("nonce length already checked"); + let aad = unsafe { core::slice::from_raw_parts(aad_ptr, aad_len) }; + let ct_with_tag = + unsafe { core::slice::from_raw_parts(ciphertext_with_tag_ptr, ciphertext_with_tag_len) }; + + if let Some(result) = chacha20_poly1305_decrypt(key_arr, nonce_arr, aad, ct_with_tag) { + Box::into_raw(Box::new(result)) + } else { + null_mut() + } +} From b5b7a9b80aa8b71fdbd857d4b06d531adf96dca2 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Fri, 27 Feb 2026 00:02:31 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC,=20=EC=82=AC=EC=86=8C=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + internal/ffi/Cargo.toml | 1 + internal/ffi/src/lib.rs | 1 + internal/ffi/src/rng_ffi.rs | 12 ------------ 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c8376f..02cff0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ entlib-native-core-secure = { path = "./crypto/core-secure", version = entlib-native-rng = { path = "./crypto/rng", version = "1.1.2-Alpha" } entlib-native-sha2 = { path = "./crypto/sha2", version = "1.1.2-Alpha" } entlib-native-sha3 = { path = "./crypto/sha3", version = "1.1.2-Alpha" } +entlib-native-chacha20 = { path = "./crypto/chacha20", version = "1.1.2-Alpha" } \ No newline at end of file diff --git a/internal/ffi/Cargo.toml b/internal/ffi/Cargo.toml index b6795ff..239ef51 100644 --- a/internal/ffi/Cargo.toml +++ b/internal/ffi/Cargo.toml @@ -27,6 +27,7 @@ entlib-native-core-secure.workspace = true entlib-native-rng.workspace = true entlib-native-sha2.workspace = true entlib-native-sha3.workspace = true +entlib-native-chacha20.workspace = true [dev-dependencies] #criterion = { version = "0.8.2", features = ["html_reports"] } diff --git a/internal/ffi/src/lib.rs b/internal/ffi/src/lib.rs index bc62987..148d1ac 100644 --- a/internal/ffi/src/lib.rs +++ b/internal/ffi/src/lib.rs @@ -1,4 +1,5 @@ pub mod base64_ffi; +mod chacha20_ffi; mod rng_ffi; // todo; 보안강화 및 검증 pub mod secure_buffer_ffi; pub mod sha2_ffi; diff --git a/internal/ffi/src/rng_ffi.rs b/internal/ffi/src/rng_ffi.rs index 434a452..b8e08c5 100644 --- a/internal/ffi/src/rng_ffi.rs +++ b/internal/ffi/src/rng_ffi.rs @@ -209,15 +209,3 @@ pub unsafe extern "C" fn entlib_rng_mixed_free(rng_ptr: *mut MixedRng) { let _ = unsafe { Box::from_raw(rng_ptr) }; } } - -// -// 보안 버퍼 (secure buffer) ffi -// - -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entlib_rng_buffer_free(buf_ptr: *mut SecureBuffer) { - if !buf_ptr.is_null() { - // drop -> zeroize 보장 및 러스트 힙 메모리 반환 - let _ = unsafe { Box::from_raw(buf_ptr) }; - } -} From 1daefdc1780c806075c68669fcf84bf1b2cae700 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Fri, 27 Feb 2026 00:02:41 +0900 Subject: [PATCH 6/7] =?UTF-8?q?IDE=20=EB=AA=A8=EB=93=88=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/entlib-native.iml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.idea/entlib-native.iml b/.idea/entlib-native.iml index fdae6f1..e6bdad6 100644 --- a/.idea/entlib-native.iml +++ b/.idea/entlib-native.iml @@ -30,6 +30,9 @@ + + + From b09ca26873a4436c923fb3c0c84b7805e3e53f09 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Fri, 27 Feb 2026 00:06:13 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=ED=81=B4=EB=A6=AC=ED=94=BC=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crypto/chacha20/src/chacha20.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/chacha20/src/chacha20.rs b/crypto/chacha20/src/chacha20.rs index 2b4d191..b5bb71d 100644 --- a/crypto/chacha20/src/chacha20.rs +++ b/crypto/chacha20/src/chacha20.rs @@ -143,8 +143,8 @@ pub fn chacha20_poly1305_decrypt( // 4. 상수-시간 tag 검증 let mut xor_diff = 0u8; - for i in 0..16 { - xor_diff |= expected_tag_secure.inner[i] ^ received_tag[i]; + for (i, item) in received_tag.iter().enumerate().take(16) { + xor_diff |= item ^ received_tag[i]; // Q. T. Felix NOTE: 클리피 수정 } let mask = xor_diff.ct_is_zero(); // valid -> 0xFF, invalid -> 0x00