
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 |