comnic's Dev&Life

[Rust 예제] 기본적인 PKI(Public Key Infrastructure) 구현 본문

Rust

[Rust 예제] 기본적인 PKI(Public Key Infrastructure) 구현

comnic 2023. 12. 14. 07:28
반응형

기본적인 PKI(Public Key Infrastructure) 구현

 

Rust에서 PKI(Public Key Infrastructure)를 구현하기 위해 가장 많이 사용되는 암호화 알고리즘 중 하나는 RSA(Rivest–Shamir–Adleman)입니다. RSA는 대표적인 공개키 알고리즘으로, 안전한 키 교환과 전자 서명을 위해 사용됩니다.

Rust를 공부하는 입장으로 먼저 RSA를 직접 구현해 보고,
이후 rsa 라이브러리를 사용해서 구현해 보겠습니다.

 

1. 키 생성

RSA는 공개키와 비밀키로 이루어져 있습니다. 키는 두 개의 소수인 p와 q를 생성하고, n = pq로 계산됩니다. 그리고 양의 정수 e와 d를 선택하여 다음의 조건을 만족하는 공개키 (e, n)과 비밀키 (d, n)를 생성합니다.

  • e와 (p-1)(q-1)이 서로소여야 합니다.
  • (ed - 1)은 (p-1)(q-1)로 나누어 떨어져야 합니다.

Rust에서는 다양한 큰 정수 처리 라이브러리를 활용할 수 있습니다. 여기서는 num-bigint 라이브러리를 사용합니다.

use num_bigint::{BigUint, ToBigUint};

fn generate_keys() -> ((BigUint, BigUint), (BigUint, BigUint)) {
    // 소수 p와 q 생성
    let p = BigUint::from(61u64);
    let q = BigUint::from(53u64);

    // n 계산
    let n = &p * &q;

    // (p-1)(q-1) 계산
    let phi = (&p - 1u64) * (&q - 1u64);

    // e 선택 (일반적으로 65537 사용)
    let e = BigUint::from(65537u64);

    // d 계산
    let d = e.modpow(&BigUint::one(), &phi);

    ((e.clone(), n.clone()), (d, n))
}

 

2. 암호화와 복호화

RSA에서는 공개키로 암호화하고 비밀키로 복호화합니다. 메시지 m에 대해 암호문 c는 다음과 같이 계산됩니다.

비밀키로 복호화할 때는 다음과 같이 계산됩니다.

fn encrypt(m: &BigUint, public_key: &(BigUint, BigUint)) -> BigUint {
    let (e, n) = public_key;
    m.modpow(e, n)
}

fn decrypt(c: &BigUint, private_key: &(BigUint, BigUint)) -> BigUint {
    let (d, n) = private_key;
    c.modpow(d, n)
}

 

3. 서명 생성과 검증

RSA는 전자 서명에도 사용됩니다. 서명 생성자는 개인 키로 메시지에 대한 서명을 생성하고, 서명 검증자는 공개 키로 받은 서명의 유효성을 확인합니다.

fn sign(m: &BigUint, private_key: &(BigUint, BigUint)) -> BigUint {
    let (d, n) = private_key;
    m.modpow(d, n)
}

fn verify(signature: &BigUint, m: &BigUint, public_key: &(BigUint, BigUint)) -> bool {
    let (e, n) = public_key;
    let decrypted = signature.modpow(e, n);
    decrypted == *m
}

 

4. RSA 라이브러리를 사용한 구현

먼저, rsa 라이브러리를 사용하여 RSA 알고리즘을 구현합니다. 이 라이브러리는 RSA 키 생성, 서명, 검증 등을 편리하게 다룰 수 있도록 도와줍니다.

먼저 cargo.toml에 사용할 라이브러리를 추가한다.

# Cargo.toml에 의존성 추가
[dependencies]
rsa = "0.9.6"
num-bigint = "0.4.4"

cargo add를 사용해 아래와 같이 추가해도 된다.

cargo add rsa
cargo add num-bigint

다음은 RSA를 사용한 PKI 구현의 예제 코드입니다

use rsa::{RSAPrivateKey, RSAPublicKey, PaddingScheme};
use num_bigint::BigUint;
use rand::rngs::OsRng;

fn main() {
    // 1. 키 생성
    let private_key = RSAPrivateKey::new(&mut OsRng, 2048).expect("Failed to generate a private key");
    let public_key = RSAPublicKey::from(&private_key);

    // 2. 서명 생성
    let message = "Hello, PKI!";
    let signature = sign(message.as_bytes(), &private_key);

    // 3. 검증
    let is_valid = verify(message.as_bytes(), &signature, &public_key);

    // 결과 출력
    if is_valid {
        println!("Signature is valid.");
    } else {
        println!("Signature is not valid.");
    }
}

// 서명 생성 함수
fn sign(message: &[u8], private_key: &RSAPrivateKey) -> Vec<u8> {
    let mut signer = rsa::Signer::new(rsa::PKCS1_PSS_SHA256, &private_key).expect("Failed to create signer");
    signer.update(message);
    signer.finalize().expect("Failed to finalize signature").to_vec()
}

// 검증 함수
fn verify(message: &[u8], signature: &[u8], public_key: &RSAPublicKey) -> bool {
    let mut verifier = rsa::Verifier::new(rsa::PKCS1_PSS_SHA256, &public_key).expect("Failed to create verifier");
    verifier.update(message);
    verifier.verify(signature)
}

이 코드에서 rsa::PKCS1_PSS_SHA256는 서명 및 검증에 사용되는 패딩 및 해시 알고리즘을 나타냅니다. 이 예제에서는 SHA-256 해시와 PSS 패딩을 사용하였습니다.

반응형
Comments