diff --git a/COMPLIANCE.md b/COMPLIANCE.md index df6f24c..96932ee 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -12,23 +12,38 @@ NIST CAVP는 단일 알고리즘에 대한 검증 작업입니다. 실제 프로 - [ ] SP 800-90A DRBG(Deterministic Random Bit Generators) +> [KCMVP](https://seed.kisa.or.kr/kisa/kcmvp/EgovVerification.do) (KS X ISO/IEC 18031) + +- [ ] Security techniques - Hash-functions - Part 3: Dedicated hash-functions (2018) + ## SHA2 -> [NIST CAVP - Secure Hashing](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing) (ISO/IEC 10118-3) +> [NIST CAVP - Secure Hashing](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing) - [ ] FIPS 180-4 SHA Test Vectors for Hashing Bit/Byte-Oriented Messages -> [KCMVP](https://seed.kisa.or.kr/kisa/kcmvp/EgovVerification.do) KS X ISO/IEC 10118-3:2001 +> [KCMVP](https://seed.kisa.or.kr/kisa/kcmvp/EgovVerification.do) (KS X ISO/IEC 10118-3:2001) - [ ] Security techniques - Hash-functions - Part 3: Dedicated hash-functions (2018) ## SHA3 -> [NIST CAVP - Secure Hashing](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing) (ISO/IEC 10118-3) +> [NIST CAVP - Secure Hashing](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing) - [X] FIPS 202 SHA-3 Hash Function Test Vectors for Hashing Bit/Byte-Oriented Messages - [X] FIPS 202 SHA-3 XOF Test Vectors for Bit/Byte-Oriented Output -> [KCMVP](https://seed.kisa.or.kr/kisa/kcmvp/EgovVerification.do) KS X ISO/IEC 10118-3:2001 +> [KCMVP](https://seed.kisa.or.kr/kisa/kcmvp/EgovVerification.do) (KS X ISO/IEC 10118-3:2001) + +- [X] Security techniques - Hash-functions - Part 3: Dedicated hash-functions (2018) + +## HKDF + +> [NIST CAVP - Key Derivation](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/key-derivation) (SP 800-108) -- [X] Security techniques - Hash-functions - Part 3: Dedicated hash-functions (2018) \ No newline at end of file +- [ ] KDF in Counter Mode Test Vectors +- [ ] KDF in Feedback Mode Test Vectors where no counter is used +- [ ] KDF in Feedback Mode Test Vectors where zero length IV is allowed +- [ ] KDF in Feedback Mode Test Vectors where zero length IV is not allowed +- [ ] KDF in Double-Pipeline Iteration Mode Test Vectors where no counter is used +- [ ] KDF in Double-Pipeline Iteration Mode Test Vectors where counter is used \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 36db20e..9940f39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["internal/*", "crypto/*"] resolver = "2" [workspace.package] -version = "1.1.2-Alpha" +version = "1.1.2-Alpha1" edition = "2024" authors = ["Q. T. Felix "] license = "MIT LICENSE" @@ -13,15 +13,20 @@ strip = true [workspace.dependencies] ### 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" } +entlib-native-ffi = { path = "./internal/ffi", version = "1.1.2-Alpha1" } +entlib-native-quantum-util = { path = "./internal/quantum-util", version = "1.1.2-Alpha1" } ### EXTERNAL DEPENDENCIES ### -tokio = { version = "1", features = ["rt", "process"] } +tokio = { version = "1", features = ["rt", "process"] } # Q. T. Felix TODO: 제거 ### 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" } -entlib-native-core-secure = { path = "./crypto/core-secure", version = "1.1.2-Alpha" } -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" } +entlib-native-base64 = { path = "./crypto/base64", version = "1.1.2-Alpha1" } +entlib-native-constant-time = { path = "./crypto/constant-time", version = "1.1.2-Alpha1" } +entlib-native-core-secure = { path = "./crypto/core-secure", version = "1.1.2-Alpha1" } +entlib-native-rng = { path = "./crypto/rng", version = "1.1.2-Alpha1" } +entlib-native-sha2 = { path = "./crypto/sha2", version = "1.1.2-Alpha1" } +entlib-native-sha3 = { path = "./crypto/sha3", version = "1.1.2-Alpha1" } +entlib-native-chacha20 = { path = "./crypto/chacha20", version = "1.1.2-Alpha1" } +entlib-native-key-establishment = { path = "./crypto/key-establishment", version = "1.1.2-Alpha1" } +entlib-native-digital-signature = { path = "./crypto/digital-signature", version = "1.1.2-Alpha1" } +entlib-native-tls = { path = "./crypto/tls", version = "1.1.2-Alpha1" } +entlib-native-hkdf = { path = "./crypto/hkdf", version = "1.1.2-Alpha1" } +entlib-native-hex = { path = "./crypto/hex", version = "1.1.2-Alpha1" } \ No newline at end of file diff --git a/crypto/constant-time/src/constant_time.rs b/crypto/constant-time/src/constant_time.rs index aaaa71a..ae3b70a 100644 --- a/crypto/constant-time/src/constant_time.rs +++ b/crypto/constant-time/src/constant_time.rs @@ -1,25 +1,3 @@ -/* - * Copyright (c) 2025-2026 Quant - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the “Software”), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - //! 상수-시간(Constant-Time) 연산을 위한 통합 트레이트 및 구현체 //! //! 이 모듈은 민감 데이터를 다룰 때 타이밍 공격(timing attack)을 방지하기 위해 diff --git a/crypto/constant-time/src/constant_time_asm.rs b/crypto/constant-time/src/constant_time_asm.rs index f1fd7d9..1150548 100644 --- a/crypto/constant-time/src/constant_time_asm.rs +++ b/crypto/constant-time/src/constant_time_asm.rs @@ -1,25 +1,3 @@ -/* - * Copyright (c) 2025-2026 Quant - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the “Software”), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - //! 아키텍처별 인라인 어셈블리 기반 상수-시간 프리미티브 //! //! # Author diff --git a/crypto/core-secure/Cargo.toml b/crypto/core-secure/Cargo.toml index 6f473db..6f251e4 100644 --- a/crypto/core-secure/Cargo.toml +++ b/crypto/core-secure/Cargo.toml @@ -6,6 +6,7 @@ authors.workspace = true license.workspace = true [dependencies] +entlib-native-constant-time.workspace = true [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } diff --git a/crypto/core-secure/src/secure_buffer.rs b/crypto/core-secure/src/secure_buffer.rs index 2e2c562..2489d65 100644 --- a/crypto/core-secure/src/secure_buffer.rs +++ b/crypto/core-secure/src/secure_buffer.rs @@ -1,58 +1,314 @@ +//! `secure_buffer.rs` 모듈은 네이티브 환경과 외부 인프라 간에 민감 데이터를 주고받거나 가공할 때, +//! 데이터의 생명주기 전반에 걸쳐 극도의 기밀성을 유지하도록 설계된 핵심 메모리 관리 모듈입니다. +//! +//! 가비지 컬렉터의 불확실한 수거 주기에 의존하여 데이터가 메모리에 장기간 방치되는 취약점을 해결하고, +//! Zero-Trust 원칙에 입각하여 외부 요인에 의한 메모리 유출 시도를 원천 차단하는 역할을 수행합니다. +//! +//! # Features +//! 이 모듈이 제공하는 구체적인 핵심 역할은 다음과 같습니다. +//! - **물리적 메모리 잠금 (Anti-Swapping)** +//! - OS 레벨의 시스템 콜([mlock], [munlock])을 직접 호출하여 버퍼가 할당된 물리적 메모리 페이지를 강제로 잠급니다. +//! 시스템의 메모리가 부족해지더라도 암호화 키나 평문 데이터가 하드 디스크의 스왑(swap) 영역이나 페이지 파일로 밀려나 +//! 영구적인 포렌식 공격의 대상이 되는 것을 방지합니다. +//! - **최적화 우회 및 강제 메모리 소거 (Zeroization)** +//! - 데이터 사용이 끝나는 즉시 메모리의 모든 바이트를 0으로 덮어써 완전히 파기합니다. 이 과정에서 [write_volatile]과 +//! 메모리 배리어([compiler_fence])를 사용하여, 불필요한 연산이라고 판단하여 소거 코드를 삭제해버리는 컴파일러의 데드 코드 +//! 제거(DCE) 최적화와 CPU의 리오더링을 완벽히 무력화합니다. +//! - **타이밍 사이드 채널 공격 방어** +//! - 민감한 버퍼의 내용을 비교할 때 조기 종료(early return)를 허용하지 않습니다. 두 버퍼의 내용이 같은지 검사할 때, +//! 데이터의 일치 여부나 틀린 바이트의 위치에 관계없이 항상 완벽하게 동일한 실행 시간이 소요되도록 강제하는 +//! 상수-시간(Constant-Time) 비교 로직을 적용하여 공격자의 유추를 차단합니다. +//! - **우발적 로깅 유출 방지 (Anti-Logging)** +//! - 시스템 크래시 덤프나 개발자의 디버그 출력 실수로 인해 메모리 안의 평문이 로그 파일에 그대로 기록되는 대형 보안 사고를 +//! 막습니다. 메모리 출력 시도 시 실제 데이터 대신 `REDACTED ...`라는 더미 문자열만 기록되도록 내부 구조를 은폐합니다. +//! - **메모리 소유권에 따른 생명주기 통제** +//! - FFI 환경에서의 유연하고 안전한 상호 작용을 위해 두 가지 패턴의 래퍼(wrapper)를 제공합니다. +//! - **`SecureBuffer`**: 네이티브 환경에서 동적으로 할당된 후 외부로 불투명 포인터를 넘길 때 사용되며, 할당부터 +//! 완전한 파기까지의 모든 생명주기를 네이티브가 전적으로 통제합니다. 즉각적인 메모리 해제 지시가 올 때까지 데이터를 안전하게 +//! 보호합니다. +//! - **`FFIExternalSecureBuffer`**: Off-Heap에서 선제적으로 할당되어 전달된 메모리 영역을 래핑할 때 +//! 사용됩니다. 네이티브의 연산이 끝난 후 소유권 해제는 외부에 위임하되, 데이터 자체에 대한 즉각적인 파기는 네이티브가 확실하게 +//! 보장하는 구조를 취합니다. +//! +//! # Usage +//! `secure_buffer.rs` 모듈은 고도의 보안이 요구되는 네이티브 환경과 Java 측 Off-Heap 메모리 간의 상호작용에 맞추어 +//! 설계되었습니다. 주요 시나리오별 사용법은 다음과 같습니다. +//! +//! ## 네이티브 환경에서 새로운 민감 데이터 생성 (`SecureBuffer`) +//! 난수 생성기나 암호화 알고리즘을 통해 Rust 측에서 새로운 키, 해시값, 암호문 등을 생성하여 반환해야 할 때 사용합니다. +//! +//! ```rust +//! use entlib_native_core_secure::secure_buffer::SecureBuffer; +//! +//! pub fn generate_secret_key() -> *mut SecureBuffer { +//! // 1. 민감 데이터 생성 (예를 들어, 하드웨어 진난수 등) +//! let raw_key = vec![0x1A, 0x2B, 0x3C, 0x4D]; +//! +//! // 2. 보안 버퍼로 래핑 (이 순간 OS 레벨 메모리 잠금 적용) +//! let secure_key = SecureBuffer::new(raw_key); +//! +//! // 3. Java로 넘기기 위해 Box로 감싸 불투명 포인터 반환 +//! Box::into_raw(Box::new(secure_key)) +//! } +//! +//! // Java 측 연산이 끝난 후 호출되는 해제 함수 +//! #[no_mangle] +//! pub extern "C" fn entlib_secure_buffer_free(ptr: *mut SecureBuffer) { +//! if !ptr.is_null() { +//! unsafe { +//! // Box::from_raw로 소유권을 가져오면 즉시 Drop이 실행되어 +//! // 강제 소거(Zeroize) 및 메모리 잠금 해제가 수행됨 +//! let _ = Box::from_raw(ptr); +//! } +//! } +//! } +//! +//! ``` +//! +//! ## Java가 제공한 Off-Heap 메모리 사용 (`FFIExternalSecureBuffer`) +//! Java 측에서 선제적으로 메모리를 확보하여 민감한 평문이나 암호문을 전달했을 때, Rust에서 이를 안전하게 다루고 소거하기 위해 +//! 사용합니다. 이 경우 일반적으로 Java 측에선 호출자 패턴을 사용합니다. +//! +//! ```rust +//! use entlib_native_core_secure::secure_buffer::FFIExternalSecureBuffer; +//! +//! #[no_mangle] +//! pub extern "C" fn process_sensitive_data(data_ptr: *mut u8, data_len: usize) { +//! // Java가 전달한 원시 포인터를 FFI 래퍼로 캡슐화 +//! let mut ext_buffer = FFIExternalSecureBuffer { +//! inner: data_ptr, +//! len: data_len, +//! }; +//! +//! // 데이터 가공 및 암호화 로직 수행 +//! let slice = unsafe { core::slice::from_raw_parts_mut(ext_buffer.inner, ext_buffer.len) }; +//! // ... (암호화 처리) ... +//! +//! // ext_buffer가 스코프를 벗어나며 Drop이 호출됨. +//! // 이때 포인터가 가리키는 실제 메모리 영역이 0으로 덮어씌워짐. +//! // (메모리 해제 권한은 여전히 Java에 있음) +//! } +//! +//! ``` +//! +//! ## 타이밍 공격을 방어하는 데이터 비교 (상수-시간 비교) +//! MAC(메시지 인증 코드)이나 해시값을 검증할 때, 실행 시간 차이를 이용한 공격을 막기 위해 사용합니다. +//! +//! ```rust +//! fn verify_mac(expected_ptr: *mut u8, expected_len: usize, actual_mac: SecureBuffer) -> bool { +//! // 외부에서 전달된 기대 MAC 값 +//! let expected_mac = FFIExternalSecureBuffer { +//! inner: expected_ptr, +//! len: expected_len, +//! }; +//! +//! // 두 버퍼의 타입이 다르더라도 길이를 맞춘 후 내부 slice를 이용해 +//! // 상수-시간(Constant-Time) 비교를 수행하도록 응용 가능 +//! +//! // 예시: FFIExternalSecureBuffer 간의 비교 +//! let is_valid = expected_mac.ct_eq(&expected_mac); +//! +//! is_valid +//! } +//! +//! ``` +//! +//! ## 명시적 조기 소거 및 안티-로깅 확인 +//! 데이터의 수명을 최소화하고 우발적인 유출을 막는 방법입니다. +//! +//! ```rust +//! fn handle_highly_classified_data() { +//! let secret = SecureBuffer::new(vec![1, 2, 3, 4, 5]); +//! +//! // 1. 실수로 로그 출력을 시도하더라도 [REDACTED SECURE BUFFER: 5 bytes] 로 출력됨 +//! println!("Extracted Secret: {:?}", secret); +//! +//! // 2. 사용이 끝난 즉시 강제 파기 (스코프 종료를 기다리지 않음) +//! secret.wipe_and_drop(); +//! +//! // 이후부터는 secret 변수 접근 불가 +//! } +//! +//! ``` +//! +//! # Authors +//! Q. T. Felix + use core::ptr::write_volatile; use core::sync::atomic::{Ordering, compiler_fence}; +use std::fmt::{Debug, Formatter}; + +use entlib_native_constant_time::constant_time::ConstantTimeOps; + +#[cfg(unix)] +unsafe extern "C" { + fn mlock(addr: *const core::ffi::c_void, len: usize) -> i32; + fn munlock(addr: *const core::ffi::c_void, len: usize) -> i32; +} /// 메모리 소거를 보장하는 보안 버퍼 구조체입니다. /// -/// 이 구조체는 쉽게 말해 Rust가 할당하고 소유하는 메모리입니다. -/// 이 네이티브 코드상에서 연산의 '결과물'을 새로 생성할 때 사용됩니다. -/// `Base64` 디코딩 결과, 암호화된 사이퍼텍스트 생성 등의 상황을 예로 -/// 들 수 있습니다. -/// -/// 민감한 데이터를 사용한 연산 후 그 결과를 Java Heap으로 전달하는 경우, -/// 가비지 컬렉터의 생명주기에 종속되어 위험에 다시 노출되는 딜레마를 가지게 -/// 됩니다. 이 문제를 해결하기 위해 단순히 `byte[]`와 같은 Java 데이터로 -/// 직렬화(serialize)하지 않고, 이 구조체에 저장합니다. +/// 이 구조체는 Rust가 할당하고 소유하는 메모리를 캡슐화합니다. +/// 네이티브 코드상에서 연산의 '결과물'을 새로 생성할 때 주로 사용됩니다. +/// (예: 인/디코딩 결과, 암호문 생성 등) /// -/// Java 측으로는 오직 해당 메모리의 원시 포인만이 전달되기 떄문에 안전한 -/// 데이터 관리가 가능합니다. +/// # Features +/// 1. 메모리 잠금 (Memory Locking): `mlock`을 사용하여 메모리가 스왑(Swap) 영역으로 +/// 페이지 아웃되는 것을 방지합니다. 이는 디스크에 평문이 남는 것을 막습니다. +/// 2. 자동 소거 (Zeroization): 구조체가 스코프를 벗어나거나 명시적으로 해제될 때, +/// `volatile` 쓰기를 통해 메모리를 0으로 덮어씁니다. +/// 3. 상수 시간 비교 (Constant-Time Comparison): 타이밍 공격을 방지하기 위한 +/// 비교 함수를 제공합니다. +/// 4. 안티 로깅 (Anti-Logging): `Debug` 트레이트 구현 시 실제 데이터를 출력하지 않습니다. pub struct SecureBuffer { pub inner: Vec, } +impl SecureBuffer { + /// 새로운 `SecureBuffer`를 할당하고 즉시 OS 레벨에서 메모리를 잠급니다. + /// + /// 주어진 `data` 벡터의 소유권을 가져오며, 해당 메모리 영역에 대해 `mlock`을 호출하여 + /// 디스크 스왑으로 인한 평문 유출을 방지합니다. + /// + /// # Arguments + /// * `data` - 보호할 데이터를 담은 바이트 벡터 + /// + /// # Safety + /// 내부적으로 `mlock` 시스템 콜을 사용합니다. `data` 벡터가 유효한 메모리를 점유하고 + /// 있으므로 안전하지만, 시스템 리소스 제한(RLIMIT_MEMLOCK)에 따라 실패할 수도 있습니다. + /// 현재 구현은 실패 시 패닉하지 않고 진행합니다(Best-effort). + pub fn new(data: Vec) -> Self { + #[cfg(unix)] + unsafe { + // 외부 의존성(libc 등) 없이 직접 시스템 콜 호출 + // Safety: data.as_ptr()은 유효한 힙 메모리를 가리키며, data.len()은 그 길이를 정확히 나타냄 + mlock(data.as_ptr() as *const core::ffi::c_void, data.len()); + } + Self { inner: data } + } + + /// 상수 시간(Constant-Time)으로 두 버퍼의 내용을 비교합니다. + /// + /// 두 버퍼의 내용이 같은지 검사하며, 비교에 걸리는 시간이 데이터의 내용에 의존하지 않도록 + /// 하여 타이밍 공격(Timing Attack)을 방지합니다. + /// + /// # Returns + /// 두 버퍼의 내용이 완전히 동일하면 `true`, 그렇지 않으면 `false`를 반환합니다. + pub fn ct_eq(&self, other: &Self) -> bool { + if self.inner.len() != other.inner.len() { + return false; + } + + // ConstantTimeOps를 활용한 상수-시간 검사 루프 + let mut is_equal: u8 = !0; // 0xFF (ct true) + for (a, b) in self.inner.iter().zip(other.inner.iter()) { + is_equal &= a.ct_eq(*b); + } + + is_equal == !0 + } + + /// 스코프 종료를 기다리지 않고 즉각적으로 메모리를 소거하고 해제합니다. + /// + /// 이 메소드를 호출하면 `Drop` 트레이트가 즉시 실행되어 메모리 소거 및 잠금 해제가 수행됩니다. + pub fn wipe_and_drop(self) { + // self가 스코프를 벗어나면서 drop() 메소드가 즉시 실행 + drop(self); + } +} + +// 안티-로깅 방어 (Crash Dump, Println 등에서 데이터 노출 방지) +impl Debug for SecureBuffer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[REDACTED SECURE BUFFER: {} bytes]", self.inner.len()) + } +} + impl Drop for SecureBuffer { fn drop(&mut self) { for byte in self.inner.iter_mut() { - // volatile write -> 컴파일러 dce 최적화 방지함 + // volatile write -> 컴파일러 최적화(DCE) 방지하여 실제 메모리에 쓰기 강제 unsafe { write_volatile(byte, 0); } } - // 메모리 배리어로 소거 순서 보장 + // 메모리 배리어로 소거 순서 보장 (컴파일러 및 CPU 리오더링 방지) compiler_fence(Ordering::SeqCst); + + // 소거가 완료된 후 메모리 잠금 해제 + #[cfg(unix)] + unsafe { + munlock( + self.inner.as_ptr() as *const core::ffi::c_void, + self.inner.len(), + ); + } } } -/// 메모리 소거를 보장하며, Java 측에서 사용되는 보안 버퍼 구조체입니다. +/// 외부(FFI)에서 할당된 메모리를 래핑하여 보안 기능을 제공하는 구조체입니다. /// -/// 이 구조체는 Java 측에서 민감 데이터를 이미 Off-Heap 영역에 할당하여 -/// 들고 있을 때 사용됩니다. +/// 주로 Java/Kotlin 측에서 Off-Heap 메모리에 할당한 민감 데이터를 +/// Rust 쪽에서 안전하게 다루거나 소거할 때 사용됩니다. /// -/// 민감한 데이터를 사용한 연산 후 그 결과를 Java Heap으로 전달하는 경우, -/// 가비지 컬렉터의 생명주기에 종속되어 위험에 다시 노출되는 딜레마를 가지게 -/// 됩니다. 이 문제를 해결하기 위해 단순히 `byte[]`와 같은 Java 데이터로 -/// 직렬화(serialize)하지 않고, 이 구조체에 저장합니다. +/// # Secure Warning +/// 이 구조체는 `Drop` 시점에 `inner` 포인터가 가리키는 메모리를 0으로 소거(Zeroize)합니다. +/// 따라서 Java 측에서 해당 메모리를 계속 사용해야 한다면 이 구조체로 래핑해서는 안 되거나, +/// 수명 관리에 각별한 주의가 필요합니다. #[repr(C)] pub struct FFIExternalSecureBuffer { + /// 외부 메모리의 시작 주소를 가리키는 포인터 pub inner: *mut u8, + /// 버퍼의 길이 (바이트 단위) pub len: usize, } +impl FFIExternalSecureBuffer { + /// FFI 버퍼 간의 상수 시간(Constant-Time) 비교를 수행합니다. + /// + /// # Safety + /// `self.inner`와 `other.inner`가 유효한 메모리를 가리키고 있어야 하며, + /// `self.len`과 `other.len`이 해당 메모리 영역의 올바른 크기여야 합니다. + /// 유효하지 않은 포인터 접근 시 정의되지 않은 동작(UB)이 발생할 수 있습니다. + pub fn ct_eq(&self, other: &Self) -> bool { + if self.len != other.len { + return false; + } + // 포인터 자체가 null인 경우 처리 + if self.inner.is_null() || other.inner.is_null() { + return self.inner == other.inner; + } + + let mut is_equal: u8 = !0; + unsafe { + // Safety: 호출자가 포인터와 길이의 유효성을 보장해야 함 + let self_slice = core::slice::from_raw_parts(self.inner, self.len); + let other_slice = core::slice::from_raw_parts(other.inner, other.len); + + for (a, b) in self_slice.iter().zip(other_slice.iter()) { + is_equal &= a.ct_eq(*b); + } + } + + is_equal == !0 + } +} + +// FFI 영역 버퍼의 안티-로깅 방어 +impl Debug for FFIExternalSecureBuffer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[REDACTED SECURE FFI BUFFER: {} bytes]", self.len) // Q. T. Felix NOTE: 더 좋은 방법이 있다면 그거 써도 됌 + } +} + impl Drop for FFIExternalSecureBuffer { fn drop(&mut self) { if self.inner.is_null() || self.len == 0 { return; } unsafe { + // Safety: FFIExternalSecureBuffer가 생성될 때 항상 유효한 포인터와 길이를 받았다고 가정함 + // Drop 시점에 해당 메모리 영역을 0으로 덮어씀 let slice = core::slice::from_raw_parts_mut(self.inner, self.len); for byte in slice.iter_mut() { write_volatile(byte, 0); diff --git a/crypto/digital-signature/Cargo.toml b/crypto/digital-signature/Cargo.toml new file mode 100644 index 0000000..7846a1d --- /dev/null +++ b/crypto/digital-signature/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "entlib-native-digital-signature" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] diff --git a/crypto/digital-signature/src/ed25519/mod.rs b/crypto/digital-signature/src/ed25519/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/crypto/digital-signature/src/ed448/mod.rs b/crypto/digital-signature/src/ed448/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/crypto/digital-signature/src/lib.rs b/crypto/digital-signature/src/lib.rs new file mode 100644 index 0000000..e800b2e --- /dev/null +++ b/crypto/digital-signature/src/lib.rs @@ -0,0 +1,2 @@ +pub mod ed25519; +pub mod ed448; \ No newline at end of file diff --git a/crypto/hex/Cargo.toml b/crypto/hex/Cargo.toml new file mode 100644 index 0000000..73c5606 --- /dev/null +++ b/crypto/hex/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "entlib-native-hex" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +entlib-native-core-secure.workspace = true +entlib-native-constant-time.workspace = true diff --git a/crypto/hex/src/hex.rs b/crypto/hex/src/hex.rs new file mode 100644 index 0000000..4f134f2 --- /dev/null +++ b/crypto/hex/src/hex.rs @@ -0,0 +1,146 @@ +use entlib_native_constant_time::constant_time::ConstantTimeOps; +use entlib_native_core_secure::secure_buffer::SecureBuffer; + +#[derive(Debug, PartialEq, Eq)] +pub enum HexError { + InvalidLength, + InvalidData, + BufferTooSmall, +} + +/// 단일 4비트(Nibble) 값을 소문자 Hex ASCII 값으로 변환합니다. +#[inline(always)] +fn ct_encode_lower(val: u8) -> u8 { + let is_digit = val.wrapping_sub(10).ct_is_negative(); // val < 10 이면 0xFF + let char_digit = val.wrapping_add(b'0'); + let char_lower = val.wrapping_sub(10).wrapping_add(b'a'); + + let mut res = 0u8; + res = char_lower.ct_select(res, !is_digit); + res = char_digit.ct_select(res, is_digit); + res +} + +/// 단일 4비트 값을 대문자 Hex ASCII 값으로 변환합니다. +#[inline(always)] +fn ct_encode_upper(val: u8) -> u8 { + let is_digit = val.wrapping_sub(10).ct_is_negative(); + let char_digit = val.wrapping_add(b'0'); + let char_upper = val.wrapping_sub(10).wrapping_add(b'A'); + + let mut res = 0u8; + res = char_upper.ct_select(res, !is_digit); + res = char_digit.ct_select(res, is_digit); + res +} + +/// Hex ASCII 문자를 4비트 값으로 디코딩합니다. (에러 여부 마스크 동시 반환) +#[inline(always)] +fn ct_decode_val(c: u8) -> (u8, u8) { + // '0'..='9' (48..=57) + let is_digit = !(c.wrapping_sub(b'0').ct_is_negative() | b'9'.wrapping_sub(c).ct_is_negative()); + let val_digit = c.wrapping_sub(b'0'); + + // 'a'..='f' (97..=102) + let is_lower = !(c.wrapping_sub(b'a').ct_is_negative() | b'f'.wrapping_sub(c).ct_is_negative()); + let val_lower = c.wrapping_sub(b'a').wrapping_add(10); + + // 'A'..='F' (65..=70) + let is_upper = !(c.wrapping_sub(b'A').ct_is_negative() | b'F'.wrapping_sub(c).ct_is_negative()); + let val_upper = c.wrapping_sub(b'A').wrapping_add(10); + + let is_valid = is_digit | is_lower | is_upper; + + let mut val = 0u8; + val = val_digit.ct_select(val, is_digit); + val = val_lower.ct_select(val, is_lower); + val = val_upper.ct_select(val, is_upper); + + (val, is_valid) +} + +// +// 피호출자(Callee) 패턴: Rust가 할당하고 Java로 포인터를 반환할 때 사용 +// + +/// 민감 데이터를 SecureBuffer 기반의 Hex로 인코딩합니다. +pub fn encode_secure(data: &[u8]) -> SecureBuffer { + let mut res = vec![0u8; data.len() * 2]; + for (i, &byte) in data.iter().enumerate() { + res[i * 2] = ct_encode_lower(byte >> 4); + res[i * 2 + 1] = ct_encode_lower(byte & 0x0F); + } + SecureBuffer { inner: res } +} + +/// Hex 문자열을 파싱하여 SecureBuffer(원시 바이트)로 안전하게 디코딩합니다. +pub fn decode_secure(hex: &str) -> Result { + let bytes = hex.as_bytes(); + if bytes.len() % 2 != 0 { + return Err(HexError::InvalidLength); + } + + let mut res = Vec::with_capacity(bytes.len() / 2); + let mut all_valid = 0xFFu8; + + for chunk in bytes.chunks_exact(2) { + let (high, high_valid) = ct_decode_val(chunk[0]); + let (low, low_valid) = ct_decode_val(chunk[1]); + + all_valid &= high_valid & low_valid; + res.push((high << 4) | low); + } + + if all_valid != 0xFF { + // 연산을 끝까지 수행하여 연산 시간 차이를 없앤 후 마지막에 일괄적으로 실패 처리 + return Err(HexError::InvalidData); + } + + Ok(SecureBuffer { inner: res }) +} + +// +// 호출자(Caller) 패턴: Java 측에서 생성된 Off-Heap 메모리에 직접 작성 +// + +pub fn encode_to_slice_ct(data: &[u8], out: &mut [u8]) -> Result<(), HexError> { + if out.len() < data.len() * 2 { + return Err(HexError::BufferTooSmall); + } + + for (i, &byte) in data.iter().enumerate() { + out[i * 2] = ct_encode_lower(byte >> 4); + out[i * 2 + 1] = ct_encode_lower(byte & 0x0F); + } + Ok(()) +} + +pub fn decode_to_slice_ct(hex: &str, out: &mut [u8]) -> Result { + let bytes = hex.as_bytes(); + if bytes.len() % 2 != 0 { + return Err(HexError::InvalidLength); + } + + let expected_len = bytes.len() / 2; + if out.len() < expected_len { + return Err(HexError::BufferTooSmall); + } + + let mut all_valid = 0xFFu8; + + for (i, chunk) in bytes.chunks_exact(2).enumerate() { + let (high, high_valid) = ct_decode_val(chunk[0]); + let (low, low_valid) = ct_decode_val(chunk[1]); + + all_valid &= high_valid & low_valid; + out[i] = (high << 4) | low; + } + + if all_valid != 0xFF { + // Q. T. Felix NOTE: 보안을 위해 실패 시 버퍼에 쓰여진 불완전한 데이터를 즉시 0으로 소거할 수도 있음 + out[..expected_len].fill(0); + return Err(HexError::InvalidData); + } + + Ok(expected_len) +} diff --git a/crypto/hex/src/lib.rs b/crypto/hex/src/lib.rs new file mode 100644 index 0000000..ce02e67 --- /dev/null +++ b/crypto/hex/src/lib.rs @@ -0,0 +1 @@ +pub mod hex; diff --git a/crypto/hex/tests/hex_test.rs b/crypto/hex/tests/hex_test.rs new file mode 100644 index 0000000..bcd0152 --- /dev/null +++ b/crypto/hex/tests/hex_test.rs @@ -0,0 +1,125 @@ +mod tests { + use entlib_native_hex::hex::{ + HexError, decode_secure, decode_to_slice_ct, encode_secure, encode_to_slice_ct, + }; + + // + // 피호출자(Callee) 패턴 테스트: SecureBuffer 반환 API + // + + #[test] + fn test_encode_secure() { + let data = b"entlib-native"; + let secure_buf = encode_secure(data); + + // SecureBuffer 내부에 올바른 인코딩 결과가 들어있는지 확인 + assert_eq!(secure_buf.inner, b"656e746c69622d6e6174697665".to_vec()); + } + + #[test] + fn test_decode_secure_valid() { + let expected = b"entlib-native"; + + // 소문자 Hex 디코딩 + let secure_buf_lower = decode_secure("656e746c69622d6e6174697665").unwrap(); + assert_eq!(secure_buf_lower.inner, expected); + + // 대문자 Hex 디코딩 + let secure_buf_upper = decode_secure("656E746C69622D6E6174697665").unwrap(); + assert_eq!(secure_buf_upper.inner, expected); + } + + #[test] + fn test_decode_secure_invalid_length() { + // 홀수 길이 입력 + let res = decode_secure("656e746c69622d6e617469766"); + + assert!(matches!(res, Err(HexError::InvalidLength))); + } + + #[test] + fn test_decode_secure_invalid_data() { + // 유효하지 않은 문자 'g' 포함 + let res = decode_secure("656e746c69622d6e6174697g65"); + + // 타이밍 공격 방어를 위해 InvalidData가 정상적으로 반환되는지 확인 + assert!(matches!(res, Err(HexError::InvalidData))); + } + + // + // 호출자(Caller) 패턴 테스트: 외부 버퍼(Slice) 직접 작성 API + // + + #[test] + fn test_encode_to_slice_ct_valid() { + let data = b"quant"; + let mut out = vec![0u8; 10]; // "quant"는 5바이트 -> 10바이트 Hex 필요 + + encode_to_slice_ct(data, &mut out).unwrap(); + assert_eq!(out, b"7175616e74"); + } + + #[test] + fn test_encode_to_slice_ct_buffer_too_small() { + let data = b"quant"; + let mut out = vec![0u8; 8]; // 공간 부족 (10바이트 필요) + + let res = encode_to_slice_ct(data, &mut out); + assert_eq!(res.unwrap_err(), HexError::BufferTooSmall); + } + + #[test] + fn test_decode_to_slice_ct_valid() { + let hex_str = "7175616e74"; // "quant" + let mut out = vec![0u8; 5]; + + let len = decode_to_slice_ct(hex_str, &mut out).unwrap(); + assert_eq!(len, 5); + assert_eq!(out, b"quant"); + } + + #[test] + fn test_decode_to_slice_ct_buffer_too_small() { + let hex_str = "7175616e74"; + let mut out = vec![0u8; 4]; // 공간 부족 (5바이트 필요) + + let res = decode_to_slice_ct(hex_str, &mut out); + assert_eq!(res.unwrap_err(), HexError::BufferTooSmall); + } + + #[test] + fn test_decode_to_slice_ct_invalid_data_zeroing() { + let hex_str = "7175616x74"; // 'x'가 포함된 잘못된 Hex 문자열 + let mut out = vec![0xFF; 5]; // 초기 버퍼 상태를 0xFF로 세팅 + + let res = decode_to_slice_ct(hex_str, &mut out); + + // 잘못된 데이터 에러를 반환해야 함 + assert_eq!(res.unwrap_err(), HexError::InvalidData); + + // [중요 보안 검증] 연산 실패 시 버퍼에 씌어졌던 가비지 데이터가 즉시 0으로 소거(Zeroing)되어야 함 + assert_eq!(out, vec![0x00, 0x00, 0x00, 0x00, 0x00]); + } + + // + // 엣지 케이스 + // + + #[test] + fn test_empty_inputs() { + // 빈 바이트 배열 인코딩 + let secure_buf = encode_secure(b""); + assert!(secure_buf.inner.is_empty()); + + let mut out_enc = vec![]; + encode_to_slice_ct(b"", &mut out_enc).unwrap(); + + // 빈 문자열 디코딩 + let secure_dec = decode_secure("").unwrap(); + assert!(secure_dec.inner.is_empty()); + + let mut out_dec = vec![]; + let len = decode_to_slice_ct("", &mut out_dec).unwrap(); + assert_eq!(len, 0); + } +} diff --git a/crypto/hkdf/Cargo.toml b/crypto/hkdf/Cargo.toml new file mode 100644 index 0000000..d2941c7 --- /dev/null +++ b/crypto/hkdf/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "entlib-native-hkdf" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +entlib-native-core-secure.workspace = true +entlib-native-sha2.workspace = true \ No newline at end of file diff --git a/crypto/hkdf/src/api.rs b/crypto/hkdf/src/api.rs new file mode 100644 index 0000000..5b3b388 --- /dev/null +++ b/crypto/hkdf/src/api.rs @@ -0,0 +1,107 @@ +use crate::core::{HkdfHash, sp800_56c_expand_counter_mode, sp800_56c_extract}; +use entlib_native_core_secure::secure_buffer::SecureBuffer; +use entlib_native_sha2::api::{SHA256, SHA512}; + +// +// 내부 트레이트 구현부 (Implementations for External Crates) +// + +impl HkdfHash for SHA256 { + const BLOCK_SIZE: usize = 64; + const OUTPUT_SIZE: usize = 32; + + fn new() -> Self { + SHA256::new() + } + fn update(&mut self, data: &[u8]) { + self.update(data) + } + fn finalize(self) -> Vec { + self.finalize() + } +} + +impl HkdfHash for SHA512 { + const BLOCK_SIZE: usize = 128; + const OUTPUT_SIZE: usize = 64; + + fn new() -> Self { + SHA512::new() + } + fn update(&mut self, data: &[u8]) { + self.update(data) + } + fn finalize(self) -> Vec { + self.finalize() + } +} + +// +// Public APIs (SP 800-56C Rev. 2 / SP 800-108 Variants) +// + +/// SP 800-56C Rev. 2 기반 KDF (SHA256) API +pub struct KdfSha256; + +impl KdfSha256 { + /// Step 1: Randomness Extraction + /// Salt와 공유 비밀키(Z)를 받아 마스터 키(K_mc)를 추출합니다. + pub fn extract(salt: Option<&[u8]>, z: &[u8]) -> SecureBuffer { + sp800_56c_extract::(salt, z) + } + + /// Step 2: Key Expansion (Counter Mode) + /// 마스터 키(K_mc), 용도 식별자(Label), 상황 정보(Context)를 받아 원하는 길이의 키를 파생합니다. + pub fn expand( + k_mc: &[u8], + label: &[u8], + context: &[u8], + len_bytes: usize, + ) -> Result { + sp800_56c_expand_counter_mode::(k_mc, label, context, len_bytes) + } + + /// Extract와 Expand를 단일 호출로 수행합니다. + pub fn oneshot( + salt: Option<&[u8]>, + z: &[u8], + label: &[u8], + context: &[u8], + len_bytes: usize, + ) -> Result { + let k_mc = Self::extract(salt, z); + Self::expand(&k_mc.inner, label, context, len_bytes) + } +} + +/// SP 800-56C Rev. 2 기반 KDF (SHA512) API +pub struct KdfSha512; + +impl KdfSha512 { + /// Step 1: Randomness Extraction + pub fn extract(salt: Option<&[u8]>, z: &[u8]) -> SecureBuffer { + sp800_56c_extract::(salt, z) + } + + /// Step 2: Key Expansion (Counter Mode) + pub fn expand( + k_mc: &[u8], + label: &[u8], + context: &[u8], + len_bytes: usize, + ) -> Result { + sp800_56c_expand_counter_mode::(k_mc, label, context, len_bytes) + } + + /// Extract와 Expand를 단일 호출로 수행합니다. + pub fn oneshot( + salt: Option<&[u8]>, + z: &[u8], + label: &[u8], + context: &[u8], + len_bytes: usize, + ) -> Result { + let k_mc = Self::extract(salt, z); + Self::expand(&k_mc.inner, label, context, len_bytes) + } +} diff --git a/crypto/hkdf/src/core.rs b/crypto/hkdf/src/core.rs new file mode 100644 index 0000000..cc69e46 --- /dev/null +++ b/crypto/hkdf/src/core.rs @@ -0,0 +1,133 @@ +//! NIST SP 800-56C Rev. 2 HKDF 구현 + +use entlib_native_core_secure::secure_buffer::SecureBuffer; +use std::cmp::min; + +/// KDF에 사용할 해시 함수의 범용 트레이트 +pub trait HkdfHash { + const BLOCK_SIZE: usize; + const OUTPUT_SIZE: usize; + + fn new() -> Self; + fn update(&mut self, data: &[u8]); + fn finalize(self) -> Vec; +} + +pub(crate) struct HmacKeyPads { + pub i_pad: Vec, + pub o_pad: Vec, +} + +impl Drop for HmacKeyPads { + fn drop(&mut self) { + // 메모리 안전 소거 + for b in self.i_pad.iter_mut() { + *b = 0; + } + for b in self.o_pad.iter_mut() { + *b = 0; + } + } +} + +pub(crate) fn precompute_hmac_pads(key: &[u8]) -> HmacKeyPads { + let mut normalized_key = vec![0u8; H::BLOCK_SIZE]; + + if key.len() > H::BLOCK_SIZE { + let mut h = H::new(); + h.update(key); + let hash = h.finalize(); + normalized_key[..hash.len()].copy_from_slice(&hash); + let _cleanup = SecureBuffer { inner: hash }; + } else { + normalized_key[..key.len()].copy_from_slice(key); + } + + let mut i_pad = vec![0u8; H::BLOCK_SIZE]; + let mut o_pad = vec![0u8; H::BLOCK_SIZE]; + + for i in 0..H::BLOCK_SIZE { + i_pad[i] = normalized_key[i] ^ 0x36; + o_pad[i] = normalized_key[i] ^ 0x5c; + } + + let _cleanup_key = SecureBuffer { + inner: normalized_key, + }; + HmacKeyPads { i_pad, o_pad } +} + +/// SP 800-56C Rev. 2 Step 1: Randomness Extraction +/// K_mc = HMAC-Hash(salt, Z) +pub(crate) fn sp800_56c_extract(salt: Option<&[u8]>, z: &[u8]) -> SecureBuffer { + // Q. T. Felix NOTE: 주의: SP 800-56C Rev 2는 Salt가 없을 경우 Hash의 'Block Size'만큼의 0 배열을 강제함 + let zero_salt = vec![0u8; H::BLOCK_SIZE]; + let actual_salt = salt.unwrap_or(&zero_salt); + + let pads = precompute_hmac_pads::(actual_salt); + + let mut h_inner = H::new(); + h_inner.update(&pads.i_pad); + h_inner.update(z); // Z is the shared secret (IKM) + let inner_res = h_inner.finalize(); + + let mut h_outer = H::new(); + h_outer.update(&pads.o_pad); + h_outer.update(&inner_res); + let k_mc = h_outer.finalize(); + + let _c1 = SecureBuffer { inner: inner_res }; + SecureBuffer { inner: k_mc } +} + +/// SP 800-56C Rev. 2 Step 2: Key Expansion (Using SP 800-108 Counter Mode) +/// K(i) = HMAC-Hash(K_mc, [i]_2 || Label || 0x00 || Context || [L]_2) +pub(crate) fn sp800_56c_expand_counter_mode( + k_mc: &[u8], + label: &[u8], + context: &[u8], + len_bytes: usize, +) -> Result { + let l_bits = (len_bytes as u64) * 8; + + // 32비트 카운터 및 L 길이 한계 검증 + if l_bits == 0 || len_bytes > (u32::MAX as usize) * H::OUTPUT_SIZE { + return Err("SP 800-108 Expand: Requested length invalid or too large"); + } + + let pads = precompute_hmac_pads::(k_mc); + let mut okm = Vec::with_capacity(len_bytes); + + let n = (len_bytes + H::OUTPUT_SIZE - 1) / H::OUTPUT_SIZE; + let l_bits_bytes = (l_bits as u32).to_be_bytes(); // [L]_2 (32-bit Big Endian) + + for i in 1..=n { + let i_bytes = (i as u32).to_be_bytes(); // [i]_2 (32-bit Big Endian) + + let mut h_in = H::new(); + h_in.update(&pads.i_pad); + + // SP 800-108 Counter Mode Data Encoding + h_in.update(&i_bytes); // 1. [i]_2 + h_in.update(label); // 2. Label + h_in.update(&[0x00]); // 3. 0x00 Separator + h_in.update(context); // 4. Context + h_in.update(&l_bits_bytes); // 5. [L]_2 + + let inner_hash = h_in.finalize(); + + let mut h_out = H::new(); + h_out.update(&pads.o_pad); + h_out.update(&inner_hash); + let t_block = h_out.finalize(); + + let remaining = len_bytes - okm.len(); + let copy_len = min(t_block.len(), remaining); + okm.extend_from_slice(&t_block[..copy_len]); + + let _c1 = SecureBuffer { inner: inner_hash }; + let _c2 = SecureBuffer { inner: t_block }; // 블록 데이터 임시 소거 + } + + Ok(SecureBuffer { inner: okm }) +} diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs new file mode 100644 index 0000000..db7a4ee --- /dev/null +++ b/crypto/hkdf/src/lib.rs @@ -0,0 +1,2 @@ +pub mod api; +mod core; diff --git a/crypto/hkdf/tests/hkdf_test.rs b/crypto/hkdf/tests/hkdf_test.rs new file mode 100644 index 0000000..677c591 --- /dev/null +++ b/crypto/hkdf/tests/hkdf_test.rs @@ -0,0 +1,98 @@ +#[cfg(test)] +mod tests { + use entlib_native_hkdf::api::KdfSha256; + + /// 16진수 문자열을 Vec로 변환하는 헬퍼 함수 + fn decode_hex(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("Hex string decoding failed")) + .collect() + } + + #[test] + fn test_sp800_56c_api_consistency() { + // API 구조 및 Extract + Expand 분할 호출과 Oneshot 호출의 결과가 일치하는지 검증 + let z = b"shared_secret_from_key_exchange_123456"; + let salt = b"random_salt_value_for_extraction"; + let label = b"KDF_Label_MasterKey"; + let context = b"KDF_Context_Session_A"; + let expected_len = 64; // SHA-256 출력(32바이트)보다 긴 64바이트 요청 (카운터 2회전) + + // 단계별 호출 (Extract -> Expand) + let k_mc = KdfSha256::extract(Some(salt), z); + let okm_step = + KdfSha256::expand(&k_mc.inner, label, context, expected_len).expect("Expand failed"); + + assert_eq!( + okm_step.inner.len(), + expected_len, + "Step-by-step OKM length mismatch" + ); + + // 단일 호출 (Oneshot) + let okm_oneshot = KdfSha256::oneshot(Some(salt), z, label, context, expected_len) + .expect("Oneshot failed"); + + assert_eq!( + okm_oneshot.inner.len(), + expected_len, + "Oneshot OKM length mismatch" + ); + + // 정합성 검증 + assert_eq!( + okm_step.inner, okm_oneshot.inner, + "Consistency Error: Oneshot result does not match Step-by-step result" + ); + } + + #[test] + fn test_sp800_56c_empty_salt_handling() { + // SP 800-56C Rev 2 규격에 따라 Salt가 제공되지 않았을 때 + // 해시 블록 사이즈(SHA-256의 경우 64바이트) 길이의 Zero 배열이 정상적으로 적용되는지 검증 + let z = decode_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); + let label = decode_hex("f0f1f2"); + let context = decode_hex("f3f4f5"); + let expected_len = 32; + + // Salt가 None일 때 패닉 없이 정상적으로 K_mc 추출 및 확장이 이루어지는지 확인 + let okm = KdfSha256::oneshot(None, &z, &label, &context, expected_len); + assert!(okm.is_ok(), "Oneshot failed with None salt"); + assert_eq!(okm.unwrap().inner.len(), expected_len); + } + + #[test] + fn test_sp800_108_counter_mode_large_output() { + // 해시 출력 길이(32바이트)의 배수가 아닌 매우 긴 데이터(예: 100바이트)를 요청했을 때, + // 카운터 루프가 정상적으로 동작하고 마지막 블록이 안전하게 잘려서(truncate 없이) 복사되는지 검증 + let z = b"ephemeral_shared_secret"; + let salt = b"salt"; + let label = b"enc_key_and_mac_key"; + let context = b"client_to_server"; + let expected_len = 100; // 100 bytes = 32 * 3 + 4 (총 4번의 카운터 루프 실행 필요) + + let okm = KdfSha256::oneshot(Some(salt), z, label, context, expected_len) + .expect("Expand failed for large output"); + + assert_eq!( + okm.inner.len(), + expected_len, + "Failed to generate exact large output length" + ); + } + + #[test] + fn test_sp800_108_length_boundary_rejection() { + // 잘못된 길이(0)를 요청했을 때 정상적으로 Err를 반환하는지 검증 + let z = b"secret"; + let label = b"label"; + let context = b"context"; + + let result = KdfSha256::oneshot(None, z, label, context, 0); + assert!( + result.is_err(), + "KDF should reject 0 length expansion request" + ); + } +} diff --git a/crypto/key-establishment/Cargo.toml b/crypto/key-establishment/Cargo.toml new file mode 100644 index 0000000..7892924 --- /dev/null +++ b/crypto/key-establishment/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "entlib-native-key-establishment" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] diff --git a/crypto/key-establishment/src/key_agreement/mod.rs b/crypto/key-establishment/src/key_agreement/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/crypto/key-establishment/src/key_exchange/mod.rs b/crypto/key-establishment/src/key_exchange/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/crypto/key-establishment/src/lib.rs b/crypto/key-establishment/src/lib.rs new file mode 100644 index 0000000..df6139d --- /dev/null +++ b/crypto/key-establishment/src/lib.rs @@ -0,0 +1,2 @@ +pub mod key_exchange; +pub mod key_agreement; \ No newline at end of file diff --git a/crypto/sha3/Cargo.toml b/crypto/sha3/Cargo.toml index dc5e9a5..f9d59ff 100644 --- a/crypto/sha3/Cargo.toml +++ b/crypto/sha3/Cargo.toml @@ -9,32 +9,31 @@ license.workspace = true crate-type = ["cdylib", "rlib"] [dependencies] -hex = "0.4.3" # 다음 커밋에 제거 [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } entlib-native-core-secure.workspace = true entlib-native-rng.workspace = true -[[bin]] -name = "cavp_sha3_bit" -path = "src/bin/cavp_sha3_bit.rs" - -[[bin]] -name = "cavp_sha3_byte" -path = "src/bin/cavp_sha3_byte.rs" - -[[bin]] -name = "cavp_shake_xof_bit" -path = "src/bin/cavp_shake_xof_bit.rs" - -[[bin]] -name = "cavp_shake_xof_byte" -path = "src/bin/cavp_shake_xof_byte.rs" - -[[bin]] -name = "kcmvp_sha3_byte" -path = "src/bin/kcmvp_sha3_byte.rs" +#[[bin]] +#name = "cavp_sha3_bit" +#path = "test-vectors/bin/cavp_sha3_bit.rs" +# +#[[bin]] +#name = "cavp_sha3_byte" +#path = "test-vectors/bin/cavp_sha3_byte.rs" +# +#[[bin]] +#name = "cavp_shake_xof_bit" +#path = "test-vectors/bin/cavp_shake_xof_bit.rs" +# +#[[bin]] +#name = "cavp_shake_xof_byte" +#path = "test-vectors/bin/cavp_shake_xof_byte.rs" +# +#[[bin]] +#name = "kcmvp_sha3_byte" +#path = "test-vectors/bin/kcmvp_sha3_byte.rs" [[bench]] name = "sha3_bench" diff --git a/crypto/sha3/src/bin/cavp_sha3_bit.rs b/crypto/sha3/src/bin/cavp_sha3_bit.rs deleted file mode 100644 index fe14385..0000000 --- a/crypto/sha3/src/bin/cavp_sha3_bit.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! 얽힘 라이브러리(EntanglementLib) entlib-native 네이티브 SHA3 제품군 통합 Bit-Oriented & Monte Carlo CAVP 검증 도구 -//! 지원 알고리즘: SHA3_224, SHA3_256, SHA3_384, SHA3_512 - -use std::env; -use std::fs::File; -use std::io::{self, BufRead, Write}; - -use entlib_native_sha3::api::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; - -/// 런타임에 해시 알고리즘을 동적으로 선택하기 위한 래퍼 열거형 -enum DynamicHasher { - Sha224(SHA3_224), - Sha256(SHA3_256), - Sha384(SHA3_384), - Sha512(SHA3_512), -} - -impl DynamicHasher { - fn new(algo: &str) -> Self { - match algo { - "224" | "SHA3_224" => DynamicHasher::Sha224(SHA3_224::new()), - "256" | "SHA3_256" => DynamicHasher::Sha256(SHA3_256::new()), - "384" | "SHA3_384" => DynamicHasher::Sha384(SHA3_384::new()), - "512" | "SHA3_512" => DynamicHasher::Sha512(SHA3_512::new()), - _ => panic!( - "지원하지 않는 알고리즘입니다. (224, 256, 384, 512 중 택일): {}", - algo - ), - } - } - - fn update(&mut self, data: &[u8]) { - match self { - DynamicHasher::Sha224(h) => h.update(data), - DynamicHasher::Sha256(h) => h.update(data), - DynamicHasher::Sha384(h) => h.update(data), - DynamicHasher::Sha512(h) => h.update(data), - } - } - // cargo run --bin cavp_sha3_bit -- 384 test-vectors/sha-3bittestvectors/SHA3_384ShortMsg.rsp && cargo run --bin cavp_sha3_bit -- 384 test-vectors/sha-3bittestvectors/SHA3_384LongMsg.rsp && cargo run --bin cavp_sha3_bit -- 384 test-vectors/sha-3bittestvectors/SHA3_384Monte.rsp && cargo run --bin cavp_sha3_bit -- 512 test-vectors/sha-3bittestvectors/SHA3_512ShortMsg.rsp && cargo run --bin cavp_sha3_bit -- 512 test-vectors/sha-3bittestvectors/SHA3_512LongMsg.rsp && cargo run --bin cavp_sha3_bit -- 512 test-vectors/sha-3bittestvectors/SHA3_512Monte.rsp - fn finalize(self) -> Vec { - match self { - DynamicHasher::Sha224(h) => h.finalize(), - DynamicHasher::Sha256(h) => h.finalize(), - DynamicHasher::Sha384(h) => h.finalize(), - DynamicHasher::Sha512(h) => h.finalize(), - } - } - - fn finalize_bits(self, last_byte: u8, valid_bits: usize) -> Vec { - match self { - DynamicHasher::Sha224(h) => h.finalize_bits(last_byte, valid_bits), - DynamicHasher::Sha256(h) => h.finalize_bits(last_byte, valid_bits), - DynamicHasher::Sha384(h) => h.finalize_bits(last_byte, valid_bits), - DynamicHasher::Sha512(h) => h.finalize_bits(last_byte, valid_bits), - } - } -} - -fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - if args.len() < 3 { - eprintln!( - "사용법: {} <224|256|384|512> [output.rsp]", - args[0] - ); - std::process::exit(1); - } - - let algo_str = &args[1]; - let input_path = &args[2]; - let output_path = args.get(3); - - println!( - "[NIST FIPS 202] SHA3-{} Bit-Oriented & Monte Carlo CAVP 검증 시작", - algo_str - ); - - let file = File::open(input_path)?; - let reader = io::BufReader::new(file); - let lines: Vec = reader.lines().map_while(Result::ok).collect(); - - let mut output_lines = Vec::new(); - let mut total = 0usize; - let mut passed = 0usize; - let mut i = 0; - - let mut current_mc_md: Vec = Vec::new(); - - while i < lines.len() { - let line = lines[i].trim(); - - if line.starts_with("Len = ") { - output_lines.push(lines[i].clone()); - let len_str = line.strip_prefix("Len = ").unwrap().trim(); - let len: usize = len_str.parse().expect("Len 파싱 실패"); - - i += 1; - let msg_line = lines[i].trim(); - let msg_hex = msg_line - .strip_prefix("Msg = ") - .unwrap_or("") - .trim() - .to_string(); - output_lines.push(lines[i].clone()); - - i += 1; - let expected_md = if i < lines.len() && lines[i].trim().starts_with("MD = ") { - lines[i] - .trim() - .strip_prefix("MD = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let rem = len % 8; - - // 동적 해셔 인스턴스 생성 - let mut hasher = DynamicHasher::new(algo_str); - - let computed_md: String = if rem == 0 { - let msg_data: Vec = if len == 0 { - vec![] - } else { - let byte_len = len / 8; - let decoded = hex::decode(&msg_hex).expect("Msg hex decode 실패"); - decoded[0..byte_len].to_vec() - }; - - hasher.update(&msg_data); - let digest = hasher.finalize(); - hex::encode(&digest).to_uppercase() - } else { - let byte_len = len.div_ceil(8); - let decoded = hex::decode(&msg_hex).expect("Msg hex decode 실패"); - let mut data = decoded[0..byte_len.min(decoded.len())].to_vec(); - - data[byte_len - 1] &= (1u8 << rem) - 1; - - let complete_bytes = &data[..byte_len - 1]; - hasher.update(complete_bytes); - - let last_byte = data[byte_len - 1]; - let digest = hasher.finalize_bits(last_byte, rem); - - hex::encode(&digest).to_uppercase() - }; - - output_lines.push(format!("MD = {}", computed_md)); - - if !expected_md.is_empty() { - total += 1; - if computed_md == expected_md { - passed += 1; - } else { - eprintln!("FAIL Len = {} bits", len); - eprintln!(" Expected: {}", expected_md); - eprintln!(" Computed: {}", computed_md); - } - } - } else if line.starts_with("Seed = ") { - output_lines.push(lines[i].clone()); - let seed_hex = line.strip_prefix("Seed = ").unwrap().trim(); - current_mc_md = hex::decode(seed_hex).expect("Seed hex decode 실패"); - } else if line.starts_with("COUNT = ") { - output_lines.push(lines[i].clone()); - let count_str = line.strip_prefix("COUNT = ").unwrap().trim(); - - i += 1; - let expected_md = if i < lines.len() && lines[i].trim().starts_with("MD = ") { - lines[i] - .trim() - .strip_prefix("MD = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let mut md = current_mc_md.clone(); - for _ in 0..1000 { - // 루프 내부에서도 동적 해셔 사용 - let mut hasher = DynamicHasher::new(algo_str); - hasher.update(&md); - md = hasher.finalize(); - } - - current_mc_md = md.clone(); - - let computed_md = hex::encode(&md).to_uppercase(); - output_lines.push(format!("MD = {}", computed_md)); - - if !expected_md.is_empty() { - total += 1; - if computed_md == expected_md { - passed += 1; - } else { - eprintln!("FAIL Monte Carlo COUNT = {}", count_str); - eprintln!(" Expected: {}", expected_md); - eprintln!(" Computed: {}", computed_md); - } - } - } else { - output_lines.push(lines[i].clone()); - } - i += 1; - } - - println!("\n=== CAVP 검증 결과 ==="); - println!("총 테스트 케이스 : {}", total); - println!("PASS : {}", passed); - println!("FAIL : {}", total - passed); - - if let Some(out_path) = output_path { - let mut f = File::create(out_path)?; - for line in &output_lines { - writeln!(f, "{}", line)?; - } - println!("응답 파일 생성: {}", out_path); - } - - if total > 0 && total == passed { - println!( - "\n모든 Bit-Oriented 및 Monte Carlo KAT 통과 (SHA3-{})", - algo_str - ); - } else { - println!("\n검증 실패에 따른 해시 처리 로직 및 입력 데이터 재확인 필요"); - } - - Ok(()) -} diff --git a/crypto/sha3/src/bin/cavp_sha3_byte.rs b/crypto/sha3/src/bin/cavp_sha3_byte.rs deleted file mode 100644 index 20dc34d..0000000 --- a/crypto/sha3/src/bin/cavp_sha3_byte.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! 얽힘 라이브러리(EntanglementLib) entlib-native 네이티브 SHA3 제품군 통합 Byte-Oriented & Monte Carlo CAVP 검증 도구 -//! 지원 알고리즘: SHA3_224, SHA3_256, SHA3_384, SHA3_512 -//! -//! 모든 입력 데이터의 Len이 8의 배수(Byte-aligned)임을 전제로 동작함 - -use std::env; -use std::fs::File; -use std::io::{self, BufRead, Write}; - -use entlib_native_sha3::api::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; - -/// 런타임에 해시 알고리즘을 동적으로 선택하기 위한 래퍼 열거형 -enum DynamicHasher { - Sha224(SHA3_224), - Sha256(SHA3_256), - Sha384(SHA3_384), - Sha512(SHA3_512), -} - -impl DynamicHasher { - fn new(algo: &str) -> Self { - match algo { - "224" | "SHA3_224" => DynamicHasher::Sha224(SHA3_224::new()), - "256" | "SHA3_256" => DynamicHasher::Sha256(SHA3_256::new()), - "384" | "SHA3_384" => DynamicHasher::Sha384(SHA3_384::new()), - "512" | "SHA3_512" => DynamicHasher::Sha512(SHA3_512::new()), - _ => panic!( - "지원하지 않는 알고리즘입니다. (224, 256, 384, 512 중 택일): {}", - algo - ), - } - } - - fn update(&mut self, data: &[u8]) { - match self { - DynamicHasher::Sha224(h) => h.update(data), - DynamicHasher::Sha256(h) => h.update(data), - DynamicHasher::Sha384(h) => h.update(data), - DynamicHasher::Sha512(h) => h.update(data), - } - } - - fn finalize(self) -> Vec { - match self { - DynamicHasher::Sha224(h) => h.finalize(), - DynamicHasher::Sha256(h) => h.finalize(), - DynamicHasher::Sha384(h) => h.finalize(), - DynamicHasher::Sha512(h) => h.finalize(), - } - } -} - -fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - if args.len() < 3 { - eprintln!( - "사용법: {} <224|256|384|512> [output.rsp]", - args[0] - ); - std::process::exit(1); - } - - let algo_str = &args[1]; - let input_path = &args[2]; - let output_path = args.get(3); - - println!( - "[NIST FIPS 202] SHA3-{} Byte-Oriented & Monte Carlo CAVP 검증 시작", - algo_str - ); - - let file = File::open(input_path)?; - let reader = io::BufReader::new(file); - let lines: Vec = reader.lines().map_while(Result::ok).collect(); - - let mut output_lines = Vec::new(); - let mut total = 0usize; - let mut passed = 0usize; - let mut i = 0; - - let mut current_mc_md: Vec = Vec::new(); - - while i < lines.len() { - let line = lines[i].trim(); - - if line.starts_with("Len = ") { - output_lines.push(lines[i].clone()); - let len_str = line.strip_prefix("Len = ").unwrap().trim(); - let len: usize = len_str.parse().expect("Len 파싱 실패"); - - // Byte-Oriented 제약 조건 확인 (8의 배수) - if !len.is_multiple_of(8) { - eprintln!( - "경고: 입력된 Len({})이 8의 배수가 아닙니다. 이 모듈은 Byte-Oriented 전용입니다.", - len - ); - } - - i += 1; - let msg_line = lines[i].trim(); - let msg_hex = msg_line - .strip_prefix("Msg = ") - .unwrap_or("") - .trim() - .to_string(); - output_lines.push(lines[i].clone()); - - i += 1; - let expected_md = if i < lines.len() && lines[i].trim().starts_with("MD = ") { - lines[i] - .trim() - .strip_prefix("MD = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let mut hasher = DynamicHasher::new(algo_str); - let msg_data: Vec = if len == 0 { - vec![] - } else { - let byte_len = len / 8; - let decoded = hex::decode(&msg_hex).expect("Msg hex decode 실패"); - // 딱 떨어지는 바이트만큼만 추출 - decoded[0..byte_len].to_vec() - }; - - // 바이트 정렬이 보장되므로 단순 update & finalize 로직 사용 - hasher.update(&msg_data); - let digest = hasher.finalize(); - let computed_md = hex::encode(&digest).to_uppercase(); - - output_lines.push(format!("MD = {}", computed_md)); - - if !expected_md.is_empty() { - total += 1; - if computed_md == expected_md { - passed += 1; - } else { - eprintln!("FAIL Len = {} bits ({} bytes)", len, len / 8); - eprintln!(" Expected: {}", expected_md); - eprintln!(" Computed: {}", computed_md); - } - } - } else if line.starts_with("Seed = ") { - output_lines.push(lines[i].clone()); - let seed_hex = line.strip_prefix("Seed = ").unwrap().trim(); - current_mc_md = hex::decode(seed_hex).expect("Seed hex decode 실패"); - } else if line.starts_with("COUNT = ") { - output_lines.push(lines[i].clone()); - let count_str = line.strip_prefix("COUNT = ").unwrap().trim(); - - i += 1; - let expected_md = if i < lines.len() && lines[i].trim().starts_with("MD = ") { - lines[i] - .trim() - .strip_prefix("MD = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let mut md = current_mc_md.clone(); - for _ in 0..1000 { - let mut hasher = DynamicHasher::new(algo_str); - hasher.update(&md); - md = hasher.finalize(); - } - - // 다음 COUNT 루프를 위한 Seed 상태 연쇄(Chain) - current_mc_md = md.clone(); - - let computed_md = hex::encode(&md).to_uppercase(); - output_lines.push(format!("MD = {}", computed_md)); - - if !expected_md.is_empty() { - total += 1; - if computed_md == expected_md { - passed += 1; - } else { - eprintln!("FAIL Monte Carlo COUNT = {}", count_str); - eprintln!(" Expected: {}", expected_md); - eprintln!(" Computed: {}", computed_md); - } - } - } else { - output_lines.push(lines[i].clone()); - } - i += 1; - } - - println!("\n=== CAVP Byte-Oriented 검증 결과 ==="); - println!("총 테스트 케이스 : {}", total); - println!("PASS : {}", passed); - println!("FAIL : {}", total - passed); - - if let Some(out_path) = output_path { - let mut f = File::create(out_path)?; - for line in &output_lines { - writeln!(f, "{}", line)?; - } - println!("응답 파일 생성: {}", out_path); - } - - if total > 0 && total == passed { - println!( - "\n모든 Byte-Oriented 및 Monte Carlo KAT 통과 (SHA3-{})", - algo_str - ); - } else { - println!("\n검증 실패에 따른 해시 처리 로직 및 입력 데이터 재확인 필요"); - } - - Ok(()) -} diff --git a/crypto/sha3/src/bin/cavp_shake_xof_bit.rs b/crypto/sha3/src/bin/cavp_shake_xof_bit.rs deleted file mode 100644 index 0133aa4..0000000 --- a/crypto/sha3/src/bin/cavp_shake_xof_bit.rs +++ /dev/null @@ -1,349 +0,0 @@ -//! 얽힘 라이브러리(EntanglementLib) entlib-native 네이티브 SHAKE128 / SHAKE256 XOF Bit-Oriented & Monte Carlo CAVP 검증 도구 -//! ShortMsg, LongMsg, VariableOut, Monte 카를로 테스트 완벽 지원 -//! -//! nist sha3vs 및 acvp 규격에 따르면 -//! - 다음 루프의 입력 메시지(MSG[i])는 항상 이전 출력(MD[i-1])의 가장 왼쪽 128비트(16바이트)를 사용해야 함 -//! - 만약 이전 출력의 길이가 128비트보다 짧을 경우, 128비트가 될 때까지 오른쪽 끝에 0을 채워 넣음 - -use std::env; -use std::fs::File; -use std::io::{self, BufRead, Write}; - -use entlib_native_sha3::api::{SHAKE128, SHAKE256}; - -enum DynamicShakeHasher { - Shake128(SHAKE128), - Shake256(SHAKE256), -} - -impl DynamicShakeHasher { - fn new(algo: &str) -> Self { - match algo.to_uppercase().as_str() { - "SHAKE128" | "128" => DynamicShakeHasher::Shake128(SHAKE128::new()), - "SHAKE256" | "256" => DynamicShakeHasher::Shake256(SHAKE256::new()), - _ => panic!( - "지원하지 않는 알고리즘입니다. (SHAKE128, SHAKE256 중 택일): {}", - algo - ), - } - } - - fn update(&mut self, data: &[u8]) { - match self { - DynamicShakeHasher::Shake128(h) => h.update(data), - DynamicShakeHasher::Shake256(h) => h.update(data), - } - } - - fn finalize(self, output_len: usize) -> Vec { - match self { - DynamicShakeHasher::Shake128(h) => h.finalize(output_len), - DynamicShakeHasher::Shake256(h) => h.finalize(output_len), - } - } - - fn finalize_bits(self, output_len: usize, last_byte: u8, valid_bits: usize) -> Vec { - match self { - DynamicShakeHasher::Shake128(h) => h.finalize_bits(output_len, last_byte, valid_bits), - DynamicShakeHasher::Shake256(h) => h.finalize_bits(output_len, last_byte, valid_bits), - } - } -} - -/// 단일 SHAKE 연산을 수행하고 Bit-Oriented 입출력 LSB 마스킹을 적용하는 헬퍼 함수 -fn compute_shake( - algo: &str, - msg_data_full: &[u8], - in_len_bits: usize, - out_len_bits: usize, -) -> String { - let mut hasher = DynamicShakeHasher::new(algo); - let in_rem = in_len_bits % 8; - let out_byte_len = out_len_bits.div_ceil(8); - - let digest = if in_len_bits == 0 { - hasher.finalize(out_byte_len) - } else if in_rem == 0 { - let in_byte_len = in_len_bits / 8; - let data = &msg_data_full[0..in_byte_len]; - hasher.update(data); - hasher.finalize(out_byte_len) - } else { - let in_byte_len = in_len_bits.div_ceil(8); - let mut data = msg_data_full[0..in_byte_len].to_vec(); - data[in_byte_len - 1] &= (1u8 << in_rem) - 1; - - let complete_bytes = &data[..in_byte_len - 1]; - hasher.update(complete_bytes); - let last_byte = data[in_byte_len - 1]; - hasher.finalize_bits(out_byte_len, last_byte, in_rem) - }; - - let mut digest_masked = digest; - let out_rem = out_len_bits % 8; - if out_rem != 0 && !digest_masked.is_empty() { - let last_idx = digest_masked.len() - 1; - digest_masked[last_idx] &= (1u8 << out_rem) - 1; - } - - hex::encode(&digest_masked).to_uppercase() -} - -fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - if args.len() < 3 { - eprintln!( - "사용법: {} [output.rsp]", - args[0] - ); - std::process::exit(1); - } - - let algo_str = &args[1]; - let input_path = &args[2]; - let output_path = args.get(3); - - println!( - "[NIST FIPS 202] {} Bit-Oriented XOF CAVP 검증 시작", - algo_str - ); - - let file = File::open(input_path)?; - let reader = io::BufReader::new(file); - let lines: Vec = reader.lines().map_while(Result::ok).collect(); - - let mut output_lines = Vec::new(); - let mut total = 0usize; - let mut passed = 0usize; - let mut i = 0; - - let mut global_output_len: Option = None; - let mut global_input_len: Option = None; - let mut global_min_out_bytes: Option = None; - let mut global_max_out_bytes: Option = None; - - let mut monte_msg: Vec = Vec::new(); - let mut current_mc_md: Vec = Vec::new(); - let mut current_out_len_bytes: usize = 0; - - while i < lines.len() { - let line = lines[i].trim(); - - if line.starts_with("[Outputlen =") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - global_output_len = Some(val_str.parse().unwrap()); - } else if line.starts_with("[Input Length =") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - global_input_len = Some(val_str.parse().unwrap()); - } else if line.starts_with("[Minimum Output Length") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - global_min_out_bytes = Some(val_str.parse::().unwrap() / 8); - } else if line.starts_with("[Maximum Output Length") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - global_max_out_bytes = Some(val_str.parse::().unwrap() / 8); - } else if line.starts_with("Msg = ") { - output_lines.push(lines[i].clone()); - let hex_str = line.strip_prefix("Msg = ").unwrap().trim(); - monte_msg = hex::decode(hex_str).expect("Msg hex decode 실패"); - } else if line.starts_with("Len = ") { - // [1] ShortMsg / LongMsg 블록 처리 - output_lines.push(lines[i].clone()); - let len: usize = line.strip_prefix("Len = ").unwrap().trim().parse().unwrap(); - - i += 1; - let msg_hex = lines[i] - .trim() - .strip_prefix("Msg = ") - .unwrap_or("") - .trim() - .to_string(); - output_lines.push(lines[i].clone()); - - i += 1; - let expected_out = if i < lines.len() && lines[i].trim().starts_with("Output = ") { - lines[i] - .trim() - .strip_prefix("Output = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let out_bits = global_output_len.expect("global [Outputlen =] 가 누락되었습니다."); - let msg_data = hex::decode(&msg_hex).unwrap_or_default(); - - let computed_out = compute_shake(algo_str, &msg_data, len, out_bits); - output_lines.push(format!("Output = {}", computed_out)); - - if !expected_out.is_empty() { - total += 1; - if computed_out == expected_out { - passed += 1; - } else { - eprintln!("FAIL Short/LongMsg Len = {}", len); - } - } - } else if line.starts_with("COUNT = ") { - // [2] VariableOut / Monte 블록 처리 - output_lines.push(lines[i].clone()); - let count: usize = line - .strip_prefix("COUNT = ") - .unwrap() - .trim() - .parse() - .unwrap(); - - i += 1; - output_lines.push(lines[i].clone()); - let out_len_bits_from_file: usize = lines[i] - .trim() - .strip_prefix("Outputlen = ") - .unwrap() - .trim() - .parse() - .unwrap(); - - i += 1; - let mut msg_data = Vec::new(); - let mut is_var_out = false; - - if i < lines.len() && lines[i].trim().starts_with("Msg = ") { - is_var_out = true; - msg_data = - hex::decode(lines[i].trim().strip_prefix("Msg = ").unwrap().trim()).unwrap(); - output_lines.push(lines[i].clone()); - i += 1; - } - - let expected_out = if i < lines.len() && lines[i].trim().starts_with("Output = ") { - lines[i] - .trim() - .strip_prefix("Output = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let computed_out: String; - if is_var_out { - // VariableOut 테스트 - let in_len_bits = - global_input_len.expect("global [Input Length =] 가 누락되었습니다."); - computed_out = - compute_shake(algo_str, &msg_data, in_len_bits, out_len_bits_from_file); - } else { - // Monte Carlo 테스트 (NIST SP 800-185 규격) - // Q. T. Felix NOTE: 이 코드로 인해 SHAKE256을 테스트할 때 16바이트가 아닌 32바이트를 잘라내어 해시 - // 입력으로 사용하게 됨. 결국 첫 번째 루프 이후부터 입력값이 스펙과 완전히 달라짐. 그래서 다 실패 - // let target_input_bytes = if algo_str.contains("128") { 16 } else { 32 }; - let target_input_bytes = 16; - - if count == 0 { - current_mc_md = monte_msg.clone(); - current_out_len_bytes = global_max_out_bytes.unwrap(); - } - - let min_out_bytes = global_min_out_bytes.unwrap(); - let max_out_bytes = global_max_out_bytes.unwrap(); - let range = max_out_bytes - min_out_bytes + 1; - - let mut md = current_mc_md.clone(); - let mut out_bytes = current_out_len_bytes; - - for _ in 0..1000 { - let mut msg_i = md.clone(); - // 이전 출력의 leftmost 128(또는 256) bits만 다음 Seed로 사용 - if msg_i.len() >= target_input_bytes { - msg_i.truncate(target_input_bytes); - } else { - msg_i.resize(target_input_bytes, 0u8); - } - - let digest_hex = - compute_shake(algo_str, &msg_i, target_input_bytes * 8, out_bytes * 8); - md = hex::decode(&digest_hex).unwrap(); - - // NIST Spec: Rightmost 16 bits of Output_i 추출 및 정수 변환 - let rightmost_16_val = if md.len() >= 2 { - let b1 = md[md.len() - 2] as usize; - let b2 = md[md.len() - 1] as usize; - (b1 << 8) | b2 - } else { - md[0] as usize - }; - - // 다음 루프에서 사용할 동적 Outputlen 업데이트 - out_bytes = min_out_bytes + (rightmost_16_val % range); - } - - current_mc_md = md.clone(); - current_out_len_bytes = out_bytes; - - computed_out = hex::encode(&md).to_uppercase(); - } - - output_lines.push(format!("Output = {}", computed_out)); - - if !expected_out.is_empty() { - total += 1; - if computed_out == expected_out { - passed += 1; - } else { - eprintln!("FAIL COUNT = {}", count); - } - } - } else { - output_lines.push(lines[i].clone()); - } - i += 1; - } - - println!("\n=== CAVP 검증 결과 ==="); - println!("총 테스트 케이스 : {}", total); - println!("PASS : {}", passed); - println!("FAIL : {}", total - passed); - - if let Some(out_path) = output_path { - let mut f = File::create(out_path)?; - for line in &output_lines { - writeln!(f, "{}", line)?; - } - println!("응답 파일 생성: {}", out_path); - } - - Ok(()) -} diff --git a/crypto/sha3/src/bin/cavp_shake_xof_byte.rs b/crypto/sha3/src/bin/cavp_shake_xof_byte.rs deleted file mode 100644 index b8a9df1..0000000 --- a/crypto/sha3/src/bin/cavp_shake_xof_byte.rs +++ /dev/null @@ -1,319 +0,0 @@ -//! 얽힘 라이브러리(EntanglementLib) entlib-native 네이티브 SHAKE128 / SHAKE256 XOF Byte-Oriented & Monte Carlo CAVP 검증 도구 -//! ShortMsg, LongMsg, VariableOut, Monte 카를로 테스트 완벽 지원 (Byte 정렬 전용) - -use std::env; -use std::fs::File; -use std::io::{self, BufRead, Write}; - -use entlib_native_sha3::api::{SHAKE128, SHAKE256}; - -enum DynamicShakeHasher { - Shake128(SHAKE128), - Shake256(SHAKE256), -} - -impl DynamicShakeHasher { - fn new(algo: &str) -> Self { - match algo.to_uppercase().as_str() { - "SHAKE128" | "128" => DynamicShakeHasher::Shake128(SHAKE128::new()), - "SHAKE256" | "256" => DynamicShakeHasher::Shake256(SHAKE256::new()), - _ => panic!( - "지원하지 않는 알고리즘입니다. (SHAKE128, SHAKE256 중 택일): {}", - algo - ), - } - } - - fn update(&mut self, data: &[u8]) { - match self { - DynamicShakeHasher::Shake128(h) => h.update(data), - DynamicShakeHasher::Shake256(h) => h.update(data), - } - } - - fn finalize(self, output_len: usize) -> Vec { - match self { - DynamicShakeHasher::Shake128(h) => h.finalize(output_len), - DynamicShakeHasher::Shake256(h) => h.finalize(output_len), - } - } -} - -/* -cargo run --bin cavp_shake_xof_byte -- 128 test-vectors/shakebytetestvectors/SHAKE128ShortMsg.rsp && -cargo run --bin cavp_shake_xof_byte -- 128 test-vectors/shakebytetestvectors/SHAKE128LongMsg.rsp && -cargo run --bin cavp_shake_xof_byte -- 128 test-vectors/shakebytetestvectors/SHAKE128Monte.rsp && -cargo run --bin cavp_shake_xof_byte -- 128 test-vectors/shakebytetestvectors/SHAKE128VariableOut.rsp && -cargo run --bin cavp_shake_xof_byte -- 256 test-vectors/shakebytetestvectors/SHAKE256ShortMsg.rsp && -cargo run --bin cavp_shake_xof_byte -- 256 test-vectors/shakebytetestvectors/SHAKE256LongMsg.rsp && -cargo run --bin cavp_shake_xof_byte -- 256 test-vectors/shakebytetestvectors/SHAKE256Monte.rsp && -cargo run --bin cavp_shake_xof_byte -- 256 test-vectors/shakebytetestvectors/SHAKE256VariableOut.rsp -*/ - -/// 단일 SHAKE 연산을 수행하는 헬퍼 함수 (바이트 정렬 보장) -fn compute_shake(algo: &str, msg_data: &[u8], out_len_bytes: usize) -> String { - let mut hasher = DynamicShakeHasher::new(algo); - hasher.update(msg_data); - let digest = hasher.finalize(out_len_bytes); - hex::encode(&digest).to_uppercase() -} - -fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - if args.len() < 3 { - eprintln!( - "사용법: {} [output.rsp]", - args[0] - ); - std::process::exit(1); - } - - let algo_str = &args[1]; - let input_path = &args[2]; - let output_path = args.get(3); - - println!( - "[NIST FIPS 202] {} Byte-Oriented XOF CAVP 검증 시작", - algo_str - ); - - let file = File::open(input_path)?; - let reader = io::BufReader::new(file); - let lines: Vec = reader.lines().map_while(Result::ok).collect(); - - let mut output_lines = Vec::new(); - let mut total = 0usize; - let mut passed = 0usize; - let mut i = 0; - - let mut global_output_len_bytes: Option = None; - let mut global_input_len_bytes: Option = None; - let mut global_min_out_bytes: Option = None; - let mut global_max_out_bytes: Option = None; - - let mut monte_msg: Vec = Vec::new(); - let mut current_mc_md: Vec = Vec::new(); - let mut current_out_len_bytes: usize = 0; - - while i < lines.len() { - let line = lines[i].trim(); - - if line.starts_with("[Outputlen =") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - // 비트 단위로 주어지는 값을 바이트로 변환 - global_output_len_bytes = Some(val_str.parse::().unwrap() / 8); - } else if line.starts_with("[Input Length =") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - global_input_len_bytes = Some(val_str.parse::().unwrap() / 8); - } else if line.starts_with("[Minimum Output Length") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - global_min_out_bytes = Some(val_str.parse::().unwrap() / 8); - } else if line.starts_with("[Maximum Output Length") { - output_lines.push(lines[i].clone()); - let val_str = line - .split('=') - .nth(1) - .unwrap() - .strip_suffix("]") - .unwrap() - .trim(); - global_max_out_bytes = Some(val_str.parse::().unwrap() / 8); - } else if line.starts_with("Msg = ") { - output_lines.push(lines[i].clone()); - let hex_str = line.strip_prefix("Msg = ").unwrap().trim(); - monte_msg = hex::decode(hex_str).expect("Msg hex decode 실패"); - } else if line.starts_with("Len = ") { - // [1] ShortMsg / LongMsg 블록 처리 - output_lines.push(lines[i].clone()); - let len_bits: usize = line.strip_prefix("Len = ").unwrap().trim().parse().unwrap(); - let len_bytes = len_bits / 8; - - i += 1; - let msg_hex = lines[i] - .trim() - .strip_prefix("Msg = ") - .unwrap_or("") - .trim() - .to_string(); - output_lines.push(lines[i].clone()); - - i += 1; - let expected_out = if i < lines.len() && lines[i].trim().starts_with("Output = ") { - lines[i] - .trim() - .strip_prefix("Output = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let out_bytes = - global_output_len_bytes.expect("global [Outputlen =] 가 누락되었습니다."); - let mut msg_data = hex::decode(&msg_hex).unwrap_or_default(); - msg_data.truncate(len_bytes); // 실제 길이만큼 자르기 - - let computed_out = compute_shake(algo_str, &msg_data, out_bytes); - output_lines.push(format!("Output = {}", computed_out)); - - if !expected_out.is_empty() { - total += 1; - if computed_out == expected_out { - passed += 1; - } else { - eprintln!("FAIL Short/LongMsg Len = {}", len_bits); - } - } - } else if line.starts_with("COUNT = ") { - // [2] VariableOut / Monte 블록 처리 - output_lines.push(lines[i].clone()); - let count: usize = line - .strip_prefix("COUNT = ") - .unwrap() - .trim() - .parse() - .unwrap(); - - i += 1; - output_lines.push(lines[i].clone()); - let out_len_bytes_from_file: usize = lines[i] - .trim() - .strip_prefix("Outputlen = ") - .unwrap() - .trim() - .parse::() - .unwrap() - / 8; - - i += 1; - let mut msg_data = Vec::new(); - let mut is_var_out = false; - - if i < lines.len() && lines[i].trim().starts_with("Msg = ") { - is_var_out = true; - msg_data = - hex::decode(lines[i].trim().strip_prefix("Msg = ").unwrap().trim()).unwrap(); - output_lines.push(lines[i].clone()); - i += 1; - } - - let expected_out = if i < lines.len() && lines[i].trim().starts_with("Output = ") { - lines[i] - .trim() - .strip_prefix("Output = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let computed_out: String; - if is_var_out { - // VariableOut 테스트 - let in_len_bytes = - global_input_len_bytes.expect("global [Input Length =] 가 누락되었습니다."); - msg_data.truncate(in_len_bytes); - computed_out = compute_shake(algo_str, &msg_data, out_len_bytes_from_file); - } else { - // Monte Carlo 테스트 (NIST SP 800-185 규격) - // 알고리즘 종류와 무관하게 이전 결과의 16바이트(128비트)만 사용 - let target_input_bytes = 16; - - if count == 0 { - current_mc_md = monte_msg.clone(); - current_out_len_bytes = global_max_out_bytes.unwrap(); - } - - let min_out_bytes = global_min_out_bytes.unwrap(); - let max_out_bytes = global_max_out_bytes.unwrap(); - let range = max_out_bytes - min_out_bytes + 1; - - let mut md = current_mc_md.clone(); - let mut out_bytes = current_out_len_bytes; - - for _ in 0..1000 { - let mut msg_i = md.clone(); - - if msg_i.len() >= target_input_bytes { - msg_i.truncate(target_input_bytes); - } else { - msg_i.resize(target_input_bytes, 0u8); - } - - let digest_hex = compute_shake(algo_str, &msg_i, out_bytes); - md = hex::decode(&digest_hex).unwrap(); - - // NIST Spec: Rightmost 16 bits of Output_i 추출 및 정수 변환 - let rightmost_16_val = if md.len() >= 2 { - let b1 = md[md.len() - 2] as usize; - let b2 = md[md.len() - 1] as usize; - (b1 << 8) | b2 - } else { - md[0] as usize - }; - - out_bytes = min_out_bytes + (rightmost_16_val % range); - } - - current_mc_md = md.clone(); - current_out_len_bytes = out_bytes; - - computed_out = hex::encode(&md).to_uppercase(); - } - - output_lines.push(format!("Output = {}", computed_out)); - - if !expected_out.is_empty() { - total += 1; - if computed_out == expected_out { - passed += 1; - } else { - eprintln!("FAIL COUNT = {}", count); - } - } - } else { - output_lines.push(lines[i].clone()); - } - i += 1; - } - - println!("\n=== CAVP 검증 결과 ==="); - println!("총 테스트 케이스 : {}", total); - println!("PASS : {}", passed); - println!("FAIL : {}", total - passed); - - if let Some(out_path) = output_path { - let mut f = File::create(out_path)?; - for line in &output_lines { - writeln!(f, "{}", line)?; - } - println!("응답 파일 생성: {}", out_path); - } - - Ok(()) -} diff --git a/crypto/sha3/src/bin/kcmvp_sha3_byte.rs b/crypto/sha3/src/bin/kcmvp_sha3_byte.rs deleted file mode 100644 index b1b7454..0000000 --- a/crypto/sha3/src/bin/kcmvp_sha3_byte.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! 얽힘 라이브러리(EntanglementLib) entlib-native 네이티브 SHA3 제품군 통합 KCMVP Byte-Oriented & Monte Carlo CAVP 검증 도구 -//! 지원 알고리즘: SHA3_224, SHA3_256, SHA3_384, SHA3_512 -//! -//! KCMVP 암호알고리즘 검증기준 V3.0에 따른 임의 메시지 검사(Monte Carlo) 규격 적용 - -use std::env; -use std::fs::File; -use std::io::{self, BufRead, Write}; - -use entlib_native_sha3::api::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; - -enum DynamicHasher { - Sha224(SHA3_224), - Sha256(SHA3_256), - Sha384(SHA3_384), - Sha512(SHA3_512), -} - -impl DynamicHasher { - fn new(algo: &str) -> Self { - match algo { - "224" | "SHA3_224" => DynamicHasher::Sha224(SHA3_224::new()), - "256" | "SHA3_256" => DynamicHasher::Sha256(SHA3_256::new()), - "384" | "SHA3_384" => DynamicHasher::Sha384(SHA3_384::new()), - "512" | "SHA3_512" => DynamicHasher::Sha512(SHA3_512::new()), - _ => panic!( - "지원하지 않는 알고리즘입니다. (224, 256, 384, 512 중 택일): {}", - algo - ), - } - } - - fn update(&mut self, data: &[u8]) { - match self { - DynamicHasher::Sha224(h) => h.update(data), - DynamicHasher::Sha256(h) => h.update(data), - DynamicHasher::Sha384(h) => h.update(data), - DynamicHasher::Sha512(h) => h.update(data), - } - } - - fn finalize(self) -> Vec { - match self { - DynamicHasher::Sha224(h) => h.finalize(), - DynamicHasher::Sha256(h) => h.finalize(), - DynamicHasher::Sha384(h) => h.finalize(), - DynamicHasher::Sha512(h) => h.finalize(), - } - } -} - -/// KCMVP 규격: N = floor(r/n) + 1 계산 함수 -fn get_kcmvp_n(algo: &str) -> usize { - match algo { - "224" | "SHA3_224" => (1152 / 224) + 1, // 6 - "256" | "SHA3_256" => (1088 / 256) + 1, // 5 - "384" | "SHA3_384" => (832 / 384) + 1, // 3 - "512" | "SHA3_512" => (576 / 512) + 1, // 2 - _ => panic!("지원하지 않는 알고리즘"), - } -} - -fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - if args.len() < 3 { - eprintln!( - "사용법: {} <224|256|384|512> [output.rsp]", - args[0] - ); - std::process::exit(1); - } - - let algo_str = &args[1]; - let input_path = &args[2]; - let output_path = args.get(3); - - println!("[KCMVP] SHA3-{} 검증 시작", algo_str); - - let file = File::open(input_path)?; - let reader = io::BufReader::new(file); - let lines: Vec = reader.lines().map_while(Result::ok).collect(); - - let mut output_lines = Vec::new(); - let mut total = 0usize; - let mut passed = 0usize; - let mut i = 0; - - let mut current_mc_md: Vec = Vec::new(); - - while i < lines.len() { - let line = lines[i].trim(); - - if line.starts_with("Len = ") { - // [1] Short / Long Message 테스트 블록 (NIST와 동일) - output_lines.push(lines[i].clone()); - let len: usize = line.strip_prefix("Len = ").unwrap().trim().parse().unwrap(); - - i += 1; - let msg_hex = lines[i] - .trim() - .strip_prefix("Msg = ") - .unwrap_or("") - .trim() - .to_string(); - output_lines.push(lines[i].clone()); - - i += 1; - let expected_md = if i < lines.len() && lines[i].trim().starts_with("MD = ") { - lines[i] - .trim() - .strip_prefix("MD = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - let mut hasher = DynamicHasher::new(algo_str); - let msg_data: Vec = if len == 0 { - vec![] - } else { - let byte_len = len / 8; - let decoded = hex::decode(&msg_hex).expect("Msg hex decode 실패"); - decoded[0..byte_len].to_vec() - }; - - hasher.update(&msg_data); - let digest = hasher.finalize(); - let computed_md = hex::encode(&digest).to_uppercase(); - - output_lines.push(format!("MD = {}", computed_md)); - - if !expected_md.is_empty() { - total += 1; - if computed_md == expected_md { - passed += 1; - } else { - eprintln!("FAIL Len = {}", len); - } - } - } else if line.starts_with("Seed = ") { - // [2] Monte Carlo Seed 초기화 - output_lines.push(lines[i].clone()); - let seed_hex = line.strip_prefix("Seed = ").unwrap().trim(); - current_mc_md = hex::decode(seed_hex).expect("Seed hex decode 실패"); - } else if line.starts_with("COUNT = ") { - // [3] Monte Carlo COUNT 루프 - output_lines.push(lines[i].clone()); - let count_str = line.strip_prefix("COUNT = ").unwrap().trim(); - - i += 1; - let expected_md = if i < lines.len() && lines[i].trim().starts_with("MD = ") { - lines[i] - .trim() - .strip_prefix("MD = ") - .unwrap() - .trim() - .to_uppercase() - } else { - i -= 1; - String::new() - }; - - // KCMVP 임의 메시지 검사 파라미터 적용 - let n_val = get_kcmvp_n(algo_str); - - // MD_{0} ~ MD_{N-1} 까지 Seed로 배열 초기화 - let mut md_array: Vec> = vec![current_mc_md.clone(); n_val]; - - for k in n_val..(1000 + n_val) { - let mut hasher = DynamicHasher::new(algo_str); - - // 순서대로 hasher에 주입하여 Concatenation (Msg_i = MD_{k-N} || ... || MD_{k-1}) - for j in 0..n_val { - hasher.update(&md_array[k - n_val + j]); - } - - let digest = hasher.finalize(); - md_array.push(digest); - } - - // 다음 COUNT 루프를 위해 Seed 갱신 (MD_{1000+N-1}) - current_mc_md = md_array[1000 + n_val - 1].clone(); - - let computed_md = hex::encode(¤t_mc_md).to_uppercase(); - output_lines.push(format!("MD = {}", computed_md)); - - if !expected_md.is_empty() { - total += 1; - if computed_md == expected_md { - passed += 1; - } else { - eprintln!("FAIL Monte Carlo COUNT = {}", count_str); - } - } - } else { - output_lines.push(lines[i].clone()); - } - i += 1; - } - - println!("\n=== KCMVP CAVP Byte-Oriented 검증 결과 ==="); - println!("총 테스트 케이스 : {}", total); - println!("PASS : {}", passed); - println!("FAIL : {}", total - passed); - - if let Some(out_path) = output_path { - let mut f = File::create(out_path)?; - for line in &output_lines { - writeln!(f, "{}", line)?; - } - println!("응답 파일 생성: {}", out_path); - } - - Ok(()) -} diff --git a/crypto/tls/Cargo.toml b/crypto/tls/Cargo.toml new file mode 100644 index 0000000..13a5a1d --- /dev/null +++ b/crypto/tls/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "entlib-native-tls" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] diff --git a/crypto/tls/src/lib.rs b/crypto/tls/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/crypto/tls/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +}