Encryption - RSA

이전에 <Hash - MD5와 SHA256>에서 해시(Hash)에 대해 설명하며 암호화(Encryption)와 다른점에 대해 간략히 알아보았다.
이번에는 암호화에 대해서 조금 더 자세히 알아보자.

암호화 (Encryption)

암호화(Encryption)평문(PlanText)암호문(CipherText)으로 변환하는 과정을 말한다. 반대로 복호화(Decryption)은 암호문을 평문으로 변환하는 과정을 말한다.

  • 암호화: 평문 -> 암호문
  • 복호화: 암호문 -> 평문

encryption

이 과정에서 암호화와 복호화에 사용하는 키(Key)를 같은 것을 사용하는 경우를 대칭키 암호화(Symmetric Key Encryption)라 부르고, 암호화와 복호화에 서로 다른 키(공개키, 비공개키)를 사용하는 경우는 비대칭키 암호화(Asymmetric Key Encryption)라고 부른다.

  • 대칭키 암호화: 암호화와 복호화에 같은 키를 사용
  • 비대칭키 암호화: 암호화와 복호화에 서로 다른 키를 사용 (공개캐, 비공개키)

대칭키 암호화 (Symmetric Key Encryption)

대칭키 암호화는 오래전부터 사용되어온 방식이다. 암호화와 복호화에 같은 키를 사용하는데 보통 PSK(Pre-Shared Key)라고도 표현한다. 영문 그대로 미리 공유된 키를 말한다. 대칭키 암호화를 위해서는 평문을 암호화해서 전달하는 송신자와 암호문을 받아 평문으로 복호화하려는 수신자가 서로 동일한 키를 암호화가 시작되기 전에 서로 공유해야 한다.

대칭키 암호화에는 DES(Data Encryption Standard), AES(Advanced Encryption Standard)와 같은 알고리즘이 대표적으로 사용된다.

그러나 대칭키 암호화 방식에는 문제가 있다. 처음 서로 키를 공유하는 과정에서 만약 누군가 중간에서 이 키를 가로챌 수 있다면, 그 후에 해당 키를 이용해 암호화되어 전달되는 암호문들은 중간에서 쉽게 복호화되어 그 내용이 전부 노출될 수 있다.

이런 문제를 근본적으로 해결하고 암호키를 쉽게 전달하기 위해 나온 방법이 비대칭키 암호화이다.

비대칭키 암호화 (Asymmetric Key Encryption)

비대칭키 암호화는 암호화와 복호화에 서로 다른 키(공개캐, 비공개키)를 사용 한다. 비대칭키 알고리즘에는 대표적으로 RSA(Rivest–Shamir–Adleman)가 사용된다.

RSA (Rivest–Shamir–Adleman)

RSA는 수학적인 기법을 통해 한 쌍의 키를 생성한다. 보통 한 쌍의 키를 각각 공개키와 비공개키라고 부르며 다음과 같은 용도로 사용된다.

  • 공개키(Publick Key): 누구에게나 공개될 수 있으며 데이터를 보내는 발신자는 공개키를 통해 정보를 암호화한다.
  • 비공개키(Private Key, 비밀키): 수신자는 비밀키를 암호화된 메세지를 복호화 하는데 사용한다. 외부에 노출되지 않도록 안전하게 보관해야 한다.

이와 같이 RSA를 이용한 공개키 암호화 방식은 비공개키를 외부에 노출할 위험이 없어 기존의 대칭키 암호화 방식의 문제를 해결하고 있다.

일반적으로는 공개키는 암호화에만 사용되고 비밀키는 복호화에만 사용된다고 잘못 알려져 있다. 공개키로 암호화된 것을 비밀키로 복호화할 수 있을 뿐만 아니라, 비밀키로 암호화된 것을 공개키를 이용해서도 복호화할 수 있다. 이러한 한 쌍의 암호화 키를 이용하는 방식이 RSA 알고리즘인 것이다.

RSA가 사용되고 있는 사례는 주변에서 생각보다 쉽게 찾을 수 있다. SSL/TLS가 통신을 암호화하기 위해 사용하는 대칭키를 교환하기 위해서 RSA(비대칭키)를 사용하고 있다.

SSL/TLS에서 암호화에 사용하려는 대칭키를 전달(공유) 할 때, 이 대칭키를 RSA의 공개키로 암호화 하여 상대방에게 전달하고, 비밀키를 가지고 있는 상대방만 이 암호화 내용을 복호화 하여 대칭키를 획득할 수 있게 하는 것이 SSL/TLS에서 RSA를 통해 대칭키를 공유하는 방식인 것이다. 이후로는 이렇게 공유된 대칭키를 이용하여 송수신자는 암호화 통신을 하게 된다.

한 가지 의문이 있을 수 있다. 처음부터 대칭키가 아닌 비대칭키 이용해서 모든 통신을 암복호화 하여 통신하면 간단하고 좋겠지만, RSA와 같은 비대칭키 암호화 방식은 복잡한 수학적 원리로 이루어져 있어 많은 CPU 리소스를 소모하게 된다. 따라서 대칭키를 공유 할 때만 비대칭키 암호화 방식으로 대칭키를 공유하고, 그 후로는 공유된 대칭키를 이용해서 CPU 리소스를 덜 소모하는 암호화 방식을 사용해서 통신하는 것이다.

RSA는 암호화뿐만 아니라 전자서명이 가능한 최초의 알고리즘으로 알려져 있다. RSA가 갖는 전자서명 기능은 인증을 요구하는 전자 상거래와 같은 곳에서 사용된다.

예제

kotlin을 이용해 간단히 RSA(public key, private key) 키를 생성하고, 이를 이용해 암복호화를 해보자.

키 생성

java.security.KeyPairGenerator를 이용하면 쉽게 RSA 키를 생성할 수 있다.

The KeyPairGenerator class is used to generate pairs of public and private keys. Key pair generators are constructed using the getInstance factory methods (static methods that return instances of a given class).
A Key pair generator for a particular algorithm creates a public/private key pair that can be used with this algorithm. It also associates algorithm-specific parameters with each of the generated keys.
There are two ways to generate a key pair: in an algorithm-independent manner, and in an algorithm-specific manner. The only difference between the two is the initialization of the object:

공개키는 X.509 형식으로 생성된다.

X.509는 공개키 인증서와 인증알고리즘의 표준 가운데에서 공개 키 기반(PKI)의 ITU-T 표준이다.

비공개키(개인키)는 PKCS #8(Public-Key Cryptography Standard) 형식으로 생성된다.

PKCS #8개인키 정보 문법 표준으로 RFC 5208에 기술되었다. 공개키 암호에서 사용되는 비밀키 값에 대한 문법을 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class RSAKeyPairGenerator {
private val privateKey: PrivateKey
private val publicKey: PublicKey

init {
val keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(1024)
val keyPair = keyGen.generateKeyPair()
this.privateKey = keyPair.private
this.publicKey = keyPair.public
}

fun writeToFile(path: String, key: String) {
val file = File(path)
file.parentFile.mkdir();

file.bufferedWriter().use {
it.write(key)
}
}

fun getPrivateKey(): PrivateKey {
return privateKey
}

fun getPublicKey(): PublicKey {
return publicKey
}
}

fun main() {
val keyPairGenerator = RSAKeyPairGenerator()
val publicKey = keyPairGenerator.getPublicKey().encoded
val privateKey = keyPairGenerator.getPrivateKey().encoded

val base64PublicKey = Base64.getEncoder().encodeToString(publicKey)
val base64PrivateKey = Base64.getEncoder().encodeToString(privateKey)

keyPairGenerator.writeToFile("RSA/publicKey", base64PublicKey)
keyPairGenerator.writeToFile("RSA/privateKey", base64PrivateKey)

println("public key: $base64PublicKey")
println("private key: $base64PrivateKey")
}

privateKey

1
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKxfz0f6BTZVergW5tZxHgUIbVmApXb01ozq11fukFsJx2WbfvxddyxU3P6NEZjoSYCM5NPjvtscMtvfErL93qa3cVrKgBXumclfMAe7ijHeesiC/0RUnSoV7ytdz9AXn0TvjzIxAibGL8HxdGGMgAtYERM7/NYJ/JU7o7jYWhKFAgMBAAECgYBIiwXUF8+rvyunX9QEOZTVr2c9vJtmRcIpigfYtMjB14q4I0m88aTe3lQnOL1IKbINTL5cwkMnOWXaDLZ058yUkfDdahjaCxg9z9/jY2cxREvMR/oWtY5FwrodDKvoTURC8Hz+TZg4a9S1NofKY8W4YBqxpW3PKvBkIVl6beECfQJBAOEW9VdkwT/zgXwUXvivZAqlbFkOkr9zBE+tvQ+QEY6rLMgIoEa1NK/xx1gWt+c6xLMjE8f7GS7HEkCbQOtlvXcCQQDEC6MnswfK7arpv02odhc92ViHCGI1W8rvFlG4SL0RHI+VxAIBaEL+1RN9k4aCSWLrCLtLzaSa+8ArFp2r0f7jAkEA3Ck+k9qjAtBEqH6sXgX/jkI7dehBNS1k3CKNt/kskyVuycFWM5LuE+IjH1ApVOwwlR8MLCC4gv6IJdU1bIm5BQJBAIr/PUSueL32WJG2Y1cnsz7U1SGYXhk65d0yU+p3GCYDvAIRoOJii+2mIVWNvXaulYXTAQiz2xtPl2Z1eIEUOMUCQEdR7nswIOfV71fp1sTngUM3GpLDrT3uA9M8/2ND3QnfrVDc+gFWkj+OmKeDsLx7Rhs+liVP9qgFppC9IJxcmyI=

publicKey

1
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsX89H+gU2VXq4FubWcR4FCG1ZgKV29NaM6tdX7pBbCcdlm378XXcsVNz+jRGY6EmAjOTT477bHDLb3xKy/d6mt3FayoAV7pnJXzAHu4ox3nrIgv9EVJ0qFe8rXc/QF59E748yMQImxi/B8XRhjIALWBETO/zWCfyVO6O42FoShQIDAQAB

암복호화

X.509 형식의 데이터를 다시 RSA 공개키로 변환하기 위해서는 X509EncodedKeySpec을 사용한다.
PKCS #8 형식의 데이터를 RSA 비공개키로 변환하기 위해서는 PKCS8EncodedKeySpec를 사용한다.

  • 공개키: X.509 -> X509EncodedKeySpec
  • 비공개키: PKCS #8 -> PKCS8EncodedKeySpec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
object RSAUtil {

private val keyFactory = KeyFactory.getInstance("RSA")

private fun getPublicKey(base64PublicKey: String): PublicKey {
val keySpec = X509EncodedKeySpec(Base64.getDecoder().decode(base64PublicKey.toByteArray()))
return keyFactory.generatePublic(keySpec)
}

private fun getPrivateKey(base64PrivateKey: String): PrivateKey {
val keySpec = PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64PrivateKey.toByteArray()))
return keyFactory.generatePrivate(keySpec)
}

fun encrypt(data: String, base64PublicKey: String): ByteArray {
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(base64PublicKey))

return cipher.doFinal(data.toByteArray())
}

private fun decrypt(data: ByteArray, privateKey: PrivateKey): String {
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.DECRYPT_MODE, privateKey)

return String(cipher.doFinal(data))
}

fun decrypt(data: String, base64PrivateKey: String): String {
return decrypt(Base64.getDecoder().decode(data.toByteArray()), getPrivateKey(base64PrivateKey))
}
}

fun main() {
val publicKey =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCya66h9+mG6z3/3lSV/IjfXFxH/Y0CPgc+YLIjui5JmbNgPC2EQ4GJPsT0CbxjcMTnrSvyj7JzBbJhzmsTBD+HuKEiQOOlGNhPS0HIDvpk+J4DDyuGoTVHnWkuxNC9+IlbkWZQMWHcQ42VCFJwvduESOhs01vSFQCRBYNzYL54HQIDAQAB"
val privateKey =
"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALJrrqH36YbrPf/eVJX8iN9cXEf9jQI+Bz5gsiO6LkmZs2A8LYRDgYk+xPQJvGNwxOetK/KPsnMFsmHOaxMEP4e4oSJA46UY2E9LQcgO+mT4ngMPK4ahNUedaS7E0L34iVuRZlAxYdxDjZUIUnC924RI6GzTW9IVAJEFg3NgvngdAgMBAAECgYEAmAd/Z03ac/dQ/gxRYPgtHL4Td9hJ5eY6v+EfCahkNpy8Jr1AP5pR70NICXWeS9FURuDdOLNO6AmrpQGBZVPSWQODDXq47CJO4bHMk55DXzPue31jva7Kk7l2ydmm2K6h3s9b+4pkYOtN3f/8tnQsbundqIXI2Uz6E8LP8ksGilUCQQDjZt8sMa9nkZ7RC5SqF8QecMKXErsf5iZroYHt+2HHaakoV+Exy+eXBgREPO7cZMC+BIglTciHFD5grCrdwQBLAkEAyNvgViJI0XUsiLlsg9RHA0DciErEkjzXMd6LcND1KspXFM6y484uN6B07SDeqG1Dyn/C+pQarpt5xER9UEU4NwJBAIiqe7fYyH0rJFKobhlnnSNaS2h2BmYecLrA3xCCwvoQw2wOnLXLwQyfvhKwuDFWkAvjN1uMCtc70F1TO5P4eU8CQGJof8ATqhOdUgVmu4jXPzeT1rib0TVIw7I2M6FBb2zYl9Ok9bZw9OniHodzfEOOzRDwianVWEFGAWGsoKzsTP8CQQDZrzW16AnCQrmr1ENCQoQonEZTAAS5FI1XhfIH4VvGq+yCyYMdB7WtQ0kUS3Qm20VfDhim55NjlZd2BPpPBmxL"

val text = "Please encrypt this data"

val encryptedString = Base64.getEncoder().encodeToString(RSAUtil.encrypt(text, publicKey))
println("encryptedString: $encryptedString")

val decryptedString = RSAUtil.decrypt(encryptedString, privateKey)
println("decryptedString: $decryptedString")
}
Author

KimJongMin

Posted on

2020-01-02

Updated on

2021-03-22

Licensed under

댓글