RSA 키를 만들고 보관하고 사용하기

ds_chanin

·

2025. 2. 17. 22:46


개요

RSA를 사용해서 클라이언트에게서 전달받은 값의 무결성을 검증하곤 한다.

엄청 예전에 만들었던 기능인데 그냥 머릿속에만 두면 언젠가 내가 어떻게 했었는지 잊지 않을까? 싶어서 끄적여본다~

 

RSA는 공개키 암호화 방식이기에 공개키는 클라이언트에게 전달되어야 한다.

전달은.. 알아서 잘 했다고 하고 (타팀분에게 DM으로 전달했던것 같음~)

비밀키는 아무도 볼 수 없도록 잘 가렸다고 가정하자~

 

구현

그럼 이제 공개키와 비밀키를 생성해야하지 않을까?

키페어를 생성하는데 이때 사용한 구현체는 KeyPairGenerator이다.

java.security.KeyPairGenerator

val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()

위와 같이 호출해서 사용하면 되는데

getInstance를 호출할 때 공개키인 RSA를 지정해주고

initialize에 적절한 keySize를 지정하여 초기화를 해주자.

그리고 generateKeyPair 메서드를 호출하면 끝이다~

 

keySize가 무엇인가 싶을텐데 이를 이해하려면 모듈러스에 대해 알고 넘어가야 한다.

RSA는 모듈러스라 불리는 값이 핵심인데 이 모듈러스는 소인수분해하기 힘들도록 큰 두개의 '소수'를 곱한 값이다.

다시말해 모듈러스 n은 소수p, q의 곱이다.

n = p * q

왜 이렇게 구성되는지 자세한 정보는 RSA에 대해 공부해보는게 더 낫다. 여기서는 어떻게 사용했는지에 대해 다루는 것이니..

 

아니 그래서 keySize가 뭔데 이렇게 돌려말하나 싶은데

keySize는 모듈러스의 길이를 비트로 표현한거다 몇 비트짜리의 모듈러스를 만들거냐~ 이말이다.

모듈러스의 크기가 크면 당연히 보안의 강도도 올라간다.

그런데 무작정 크게만 하면 암호화랑 복호화 할때 성능에 이슈가 있을수 있다나~

보통 사용하는 길이는 1024, 2048, 4096과 같다고 한다.

그래서 나는 2048을 했고 성능에 이슈가 있던적은 한번도 없었다.

 

분해해서 관리

그래서 이 값을 어떻게 보나? 싶을텐데

생성된 keyPair의 api로 public이랑 private이 있다.

val public: PublicKey = keyPair.public
val private: PrivateKey = keyPair.private

val base64EncodedPublicKey = Base64.getEncoder().encodeToString(public.encoded)
val base64EncodedPrivateKey = Base64.getEncoder().encodeToString(private.encoded)

근데 코드로 보다시피 PublicKey, PrivateKey 객체로 제공하고 있어서 byte 배열로 그 값을 확인할 수 있다.

아무래도 우리는 컴퓨터가 아니니 바이트 배열을 보기 쉽도록 base64로 인코딩하면 대충 그 형태를 볼 수 있다.

 

이렇게 만들어진 공개키는 "base64로 인코딩 된 값이니 디코딩해서 사용해주세요~" 했던것 같다.

어쨋든 공개키로 암호화 된 값은 오로지 개인키만으로 복호화가 가능하니 이를 어떻게 관리할까 했던것 같다.

그런데 이 개인키 녀석이 base64로 인코딩 하면 길이가 너무 길더라 이거다. 1600자가 넘었던것 같다.

 

그래서 더 분해해서 관리했는데

RSA에서 사용되는 공개키건, 비밀키건 두가지 요소로 분리가 가능하다.

앞서 말했던 모듈러스(modulus)라는 녀석과 지수를 의미하는 익스포넌트(exponent)이다.

java.security.KeyFactory

val keyFactory = KeyFactory.getInstance("RSA")
val privateSpec: RSAPrivateKeySpec = keyFactory.getKeySpec(keyPair.private, RSAPrivateKeySpec::class.java)

val privateKeyModulus = privateSpec.modulus.toString(16)
val privateKeyExponent = privateSpec.privateExponent.toString(16)

각각의 값은 위와 같이 16진수로 표현된 값으로 확인해볼 수 있다.

 

이렇게 분해된 값을 별도로 보관하였고 애플리케이션이 뜰 때 다시 재조립해서 사용했다.

(물론 이렇게 plain한 값을 그대로 보관하진 않았고 이 값들을 한번 더 암호화하긴 했다.)

val keyFactory = KeyFactory.getInstance("RSA")

val privateKey = keyFactory.generatePrivate(
            RSAPrivateKeySpec(
                BigInteger(privateKey.modulus, 16),
                BigInteger(privateKey.exponent, 16)
            )
        )

공개키도 스펙이 Private이 아닌 Public을 쓰면되니 동일하게하면 된다. 

 

그래서 클라이언트가 전달해준 encryptedText가 있을때 다음과 같이 복호화 하면 된다.

val cipher = Cipher.getInstance("RSA")
cipher.init(2, privateKey) // 2는 복호화 모드다.

//전달받은 암호화 값이 base64로 인코딩 되어있다고 가정하였다면
val encryptedText = Base64.getDecoder().decode(encodedEncryptedText)
val decryptedBytes = cipher.doFinal(encryptedText)
val decodedText = String(decryptedBytes)

암호화는 위 복호화 순서를 역순으로 수행하면 되니 따로 기재하지는 않겠다~

'스터디 > Kotlin' 카테고리의 다른 글

코틀린 초간단 sha-256 만들기  (2) 2023.06.14
Resilience4j CircuitBreaker 사용하기  (0) 2021.09.26