์ํธํ์ ๋ ์ถ: ๋์นญํค vs ๋น๋์นญํค
์ํธํ ์์คํ ์ ํฌ๊ฒ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ๋๋๋ค.
- ๋์นญํค ์ํธํ: ํ๋์ ๋น๋ฐํค๋ก ์ํธํ์ ๋ณตํธํ๋ฅผ ๋ชจ๋ ์ํ. ๋ํ์ ์ผ๋ก AES. ๋น ๋ฅด๊ณ ๊ณ์ฐ๋์ด ์ ์ด ๋์ฉ๋ ๋ฐ์ดํฐ ์ํธํ์ ์ ํฉ.
- ๋น๋์นญํค ์ํธํ: ๊ณต๊ฐํค์ ๋น๊ณต๊ฐํค๋ผ๋ ๋ ๊ฐ์ ํค๋ฅผ ์ฌ์ฉ. ๋ํ์ ์ผ๋ก RSA๊ฐ ์์ผ๋ฉฐ, ํ ํค๋ก ์ํธํํ ๋ฐ์ดํฐ๋ ๋ฐ๋์ ๋ค๋ฅธ ํค๋ก๋ง ๋ณตํธํ ๊ฐ๋ฅ. ํค ๊ตํ, ์ธ์ฆ, ์๋ช ๋ฑ์ ์ฌ์ฉ
โก ์ฑ๋ฅ ์ธก๋ฉด์์์ ์ฐจ์ด
ํญ๋ชฉ ๋์นญํค (AES) ๋น๋์นญํค (RSA)
์๋ | ๋งค์ฐ ๋น ๋ฆ | ์๋์ ์ผ๋ก ๋๋ฆผ |
์ฐ์ฐ๋ | ์ ์ | ๋ง์ (ํฐ ์ ์ฐ์ฐ) |
์์ ์ฌ์ฉ | ๋ฎ์ | ๋์ |
์ฌ์ฉ ์ฉ๋ | ๋ฉ์์ง/ํ์ผ ์ํธํ | ํค ์ ๋ฌ/์๋ช /์ธ์ฆ |
์ด๋ฌํ ์ฑ๋ฅ ์ฐจ์ด๋ก ์ธํด ์ค์ ๋ณด์ ์์คํ ์์๋ โํ์ด๋ธ๋ฆฌ๋ ์ํธํโ๋ผ๋ ๋ฐฉ์์ด ๋ฑ์ฅ
ํ์ด๋ธ๋ฆฌ๋ ์ํธํ๋?
๋น ๋ฅด๊ณ ํจ์จ์ ์ธ AES๋ก ๋ฉ์์ง๋ฅผ ์ํธํํ๊ณ , ์ด AES ํค๋ฅผ ์์ ์์ ๊ณต๊ฐํค(RSA)๋ก ์ํธํํ๋ ๋ฐฉ์
์ ์ก ํ๋ฆ:
- ํด๋ผ์ด์ธํธ๋ ๋๋ค AES ํค๋ฅผ ์์ฑ
- ๋ฉ์์ง๋ฅผ AES ํค๋ก ์ํธํ (IV ํฌํจ)
- AES ํค๋ฅผ ์๋๋ฐฉ์ ๊ณต๊ฐํค๋ก RSA ์ํธํ
- ์ํธ๋ฌธ + ์ํธํ๋ AES ํค๋ฅผ ์๋ฒ์ ์ ์ฅ ๋๋ ์ ์ก
์์ ์๋:
- ์์ ์ ๋น๊ณต๊ฐํค๋ก AES ํค๋ฅผ ๋ณตํธํ
- ํด๋น AES ํค๋ก ๋ฉ์์ง๋ฅผ ๋ณตํธํ
์ด ๊ตฌ์กฐ๋ฅผ ํตํด ์๋์ ๋ณด์์ ๋์์ ํ๋ณด
IV๋? (Initialization Vector): AES ๋ธ๋ก ์ํธํ์์ ์ฒซ ๋ธ๋ก์ ๋๋คํํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ 16๋ฐ์ดํธ ๊ฐ. ์ํธ๋ฌธ์ ํฌํจ๋๋ฉฐ ๊ณต๊ฐ๋์ด๋ ๋ณด์์ ๋ฌธ์ ๊ฐ ์์.
์ค์ ๊ตฌํ ์ ์ํธ๋ฌธ ๊ตฌ์กฐ
{
"encryptedMessage": "<IV + AES ์ํธ๋ฌธ>",
"encryptedAesKey": "<RSA๋ก ์ํธํ๋ AES ํค>"
}
์ ํด๋ผ์ด์ธํธ์์ ์๋ณตํธํ๋ฅผ ํ ๊น?
- ์๋ฒ์์ ์๋ณตํธํ๋ฅผ ํ๋ฉด ์๋ฒ๊ฐ ํ๋ฌธ์ ๋ณด๊ฒ ๋๋ฏ๋ก ์ง์ ํ ๋ณด์์ด ์ด๋ ค์
- ํด๋ผ์ด์ธํธ์์ ํค๋ฅผ ์์ฑํ๊ณ , ์ํธํ๋ฅผ ์ํํ๋ฉฐ ์๋ฒ๋ ๋จ์ํ ์ ๋ฌ์ ์ญํ ๋ง ์ํํ๋ฉด ์ง์ ํ ์ข ๋จ๊ฐ ์ํธํ (E2EE) ๊ตฌ์กฐ๊ฐ ์์ฑ.
ํด๋ผ์ด์ธํธ ์ค์ฌ E2EE ํ๋ฆ ์ ๋ฆฌ
- ํด๋ผ์ด์ธํธ๊ฐ ๊ณต๊ฐํค๋ฅผ ์๋ฒ์ ๋ฑ๋ก
- ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ธฐ ์ ์๋๋ฐฉ์ ๊ณต๊ฐํค๋ฅผ ์๋ฒ์์ ์กฐํ
- AES ํค ์์ฑ โ ๋ฉ์์ง๋ฅผ ์ํธํ โ AES ํค๋ ์๋์ ๊ณต๊ฐํค๋ก ์ํธํ
- ์์ ์๋ ์์ ์ ๋น๊ณต๊ฐํค๋ก AES ํค ๋ณตํธํ โ ๋ฉ์์ง ๋ณตํธํ
โ ๊ฒฐ๋ก
- ๋์นญํค๋ ๋น ๋ฅด์ง๋ง ํค ์ ๋ฌ์ด ๋ฌธ์
- ๋น๋์นญํค๋ ๋๋ฆฌ์ง๋ง ํค ์ ๋ฌ์ ๊ฐ๋ ฅํจ
- ๊ทธ๋์ ํ์ด๋ธ๋ฆฌ๋ ์ํธํ๋ฅผ ํตํด ๋์ ์ฅ์ ์ ์กฐํฉ
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ณตํธํ๋ฅผ ์ฃผ๋ํ๋ฉด ์ง์ ํ ์ข ๋จ๊ฐ ๋ณด์(E2EE)์ด ๊ฐ๋ฅํจ
์ฝ๋๋ก ์ดํดํ๊ธฐ
๐กํด๋ผ์ด์ธํธ์ ์ญํ ์ด ๋๋ฌด๋๋ ๋ช ํํ ์ฃผ์ ๋ผ ํด๋ผ์ด์ธํธ๋ฅผ ํจ๊ป ๊ตฌํํ๊ณ ์ถ์์ง๋ง, ๋น ๋ฅธ ์ดํด์ ํ์ต์ ์ํด SpringBoot๋ก E2EE ๋ชจ๋ Flow๋ฅผ ๊ตฌํํ์๋ค.
์ฝ๋๋ฅผ ๊ตฌํํ๋ฉด์ ์๊ฒ๋ ์๋ก์ด ์ฌ์ค์ด ์๋ค.
์ผ๋ฐ ํด๋ผ-์๋ฒ ๊ด๊ณ๋ผ๋ฉด ์๋ฒ๋ ๋จ์ํ ํด๋ผ์์ ์ ์กํ publicKey๋ง ๋ค๊ณ ์์ผ๋ฉฐ, keyPair์์ฑ, ๋ฉ์ธ์ง ์/๋ณตํธํ์ ๋กค์ ๋ชจ๋ ํด๋ผ๊ฐ ๊ฐ๋๋ค. ๋ํ, ๋น๋์นญํค ์/๋ณตํธํ๋ ๋ง์ computing ์์์ด ํ์ํ๊ธฐ ๋๋ฌธ์, ๋น๋์นญํค๋ก ์ํธํ๋ ๋์นญํค๋ฅผ ๋งค๋ฒ ๋ณ๊ฒฝํ์ง ์๋๋ค.
์ฆ, ํด๋ผ์์ ๋น๋์นญํค๋ก ๋ณตํธํํ ๋ฉ์ธ์ง ๋์นญํค๋ฅผ Caching ํด๋๊ณ , ๋น ๋ฅด๊ฒ ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํ ํ ์ ์๋๋ก ํ๋ค.
Service
@Service
class EncryptionService {
/**
* AES: ๋์นญํค ๋ธ๋ก ์ํธํ ์๊ณ ๋ฆฌ์ฆ
* iv: ๋์ผํ ์ํธํ ๊ฒฐ๊ณผ๊ฐ ๋์ค์ง ์๋๋ก ๋ถ์ฌ์ฃผ๋ ๋๋ค ๊ฐ (ํค๋ ๋ฌด๊ด)
*/
// AES ๋์นญํค ์์ฑ: 256 ๋นํธ ๊ธธ์ด (๋ฉ์ธ์ง ๋ณธ๋ฌธ ์ํธํ ํ๋๋ฐ ์ฌ์ฉ)
fun generateAESKey(): SecretKey {
val keyGen = KeyGenerator.getInstance("AES")
keyGen.init(256)
return keyGen.generateKey()
}
// ํ๋ฌธ ๋ฉ์ธ์ง ์ํธํ: ๋์นญํค(secretKey) ์ฌ์ฉ
fun encryptAESWithIVPrefixed(plainText: String, secretKey: SecretKey): String {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์์ฑ
val ivBytes = ByteArray(16) // 16 ๋ฐ์ดํธ ์ง๋ฆฌ ๋ฐฐ์ด ์์ฑ
SecureRandom().nextBytes(ivBytes) // ivBytes ๋ฐฐ์ด์ ๋ฌด์์ ๊ฐ ์ฑ์
val ivSpec = IvParameterSpec(ivBytes)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec) // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์ด๊ธฐํ
val encryptedBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))
val combined = ivBytes + encryptedBytes // ๋ฉ์ธ์ง ์์ 16 ๋ฐ์ดํธ๋ฅผ ๋ถ์ฌ์, ๋ฉ์ธ์ง ์์ฑ
return Base64.getEncoder().encodeToString(combined)
}
// ๋ฉ์ ์ง ๋ณตํธํ: ๋์นญํค(SecretKey) ์ฌ์ฉ
fun decryptAESWithIVPrefixed(encryptedCombined: String, secretKey: SecretKey): String {
val combinedBytes = Base64.getDecoder().decode(encryptedCombined)
val iv = combinedBytes.copyOfRange(0, 16) // ์ํธํ์ ์ฌ์ฉํ iv ์ถ์ถ
val encrypted = combinedBytes.copyOfRange(16, combinedBytes.size) // ์ํธํ๋ ์ค์ ๋ฉ์ธ์ง ์ถ์ถ
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์์ฑ
val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec) // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์ด๊ธฐํ - Decrypt ๋ชจ๋
val decryptedBytes = cipher.doFinal(encrypted)
return String(decryptedBytes, Charsets.UTF_8)
}
// ๋์นญํค ์ํธํ: ์์ ์์ ๊ณต๊ฐํค๋ก ์ํธํ ํ์ฌ ์ ๋ฌํ ์ ์๋๋ก ํจ
fun encryptAESKeyWithRSA(secretKey: SecretKey, publicKey: PublicKey): String {
val cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val encryptedKey = cipher.doFinal(secretKey.encoded)
return Base64.getEncoder().encodeToString(encryptedKey)
}
// ๋์นญํค ๋ณตํธํ: ๋น๊ณต๊ฐํค๋ก ๋ณตํธํ
fun decryptAESKeyWithRSA(encryptedAesKey: String, privateKey: PrivateKey): SecretKey {
val cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.DECRYPT_MODE, privateKey)
val decodedKey = cipher.doFinal(Base64.getDecoder().decode(encryptedAesKey))
return SecretKeySpec(decodedKey, "AES")
}
}
generateAESKey
: ๋ฉ์ธ์ง๋ฅผ ์ํธํํ๊ธฐ ์ํ ๋๋ค ํค๋ฅผ ์์ฑencryptAESKeyWithRSA
/decryptAESKeyWithRSA
: ๋์นญํค ์/๋ณตํธํ ์ฝ๋- ์ํธํ: ์์ ์์ ๊ณต๊ฐํค๋ก ๋์นญํค๋ฅผ ์ํธํํ๋ค.
- ๋ณตํธํ: ๋ณธ์ธ์ ๋น๊ณต๊ฐํค๋ก ๋์นญํค๋ฅผ ๋ณตํธํํ๋ค.
encryptAESWithIVPrefixed
/decryptAESWithIVPrefixed
: ๋ฉ์ธ์ง ์/๋ณตํธํ ์ฝ๋- ์ํธํ: ๋์นญํค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์ธ์ง๋ฅผ ์ํธํํ๋ค. ์ด๋ ๋ฉ์ธ์ง ์์ iv๋ฅผ ๋ถ์ฌ์, ๊ฐ์ ๋ด์ฉ์ ๋ํด ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋์ค์ง ์๋๋ก ํ๋ค.
- ๋ณตํธํ: ๋์นญํค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํํ๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก iv bytes๋ฅผ ์ ๊ฑฐํ๊ณ , ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํ ํ๋ค.
Controller
@RequestMapping("/api")
class MessageController(
private val encryptionService: EncryptionService,
) {
private val publicKeyMap: MutableMap<String, PublicKey> = mutableMapOf()
private val privateKeyMap: MutableMap<String, PrivateKey> = mutableMapOf()
private val messageBox: MutableMap<String, MutableList<EncryptedPayload>> = mutableMapOf()
@PostMapping("/register")
fun registerUser(@RequestBody request: PublicKeyRegisterRequest): String {
// userId์ ๋ํ KeyPair ์์ฑ
val keyPairGen = KeyPairGenerator.getInstance("RSA")
keyPairGen.initialize(2048)
val keyPair = keyPairGen.generateKeyPair()
publicKeyMap[request.userId] = keyPair.public
privateKeyMap[request.userId] = keyPair.private
return "User ${request.userId} registered with key pair"
}
@PostMapping("/send")
fun sendMessage(@RequestBody request: MessageRequest): String {
val receiverPublicKey = publicKeyMap[request.receiverId] ?: error("Receiver not found")
val aesKey = encryptionService.generateAESKey()
// ๋ฉ์ธ์ง ์ํธํ(๋์นญํค)
val encryptedMessage = encryptionService.encryptAESWithIVPrefixed(request.message, aesKey)
// ๋์นญํค ์ํธํ(๋น๋์นญํค)
val encryptedAesKey = encryptionService.encryptAESKeyWithRSA(aesKey, receiverPublicKey)
val payload = EncryptedPayload(request.senderId, encryptedMessage, encryptedAesKey)
messageBox.computeIfAbsent(request.receiverId) { mutableListOf() }.add(payload)
return "Message sent securely to ${request.receiverId}"
}
@GetMapping("/messages/{userId}")
fun receiveMessages(@PathVariable userId: String): List<MessageResponse> {
val privateKey = privateKeyMap[userId] ?: error("Private key not found for $userId")
val messages = messageBox[userId] ?: return emptyList()
return messages.map { payload ->
// ๋์นญํค ๋ณตํธํ(๋น๋์นญํค)
val aesKey = encryptionService.decryptAESKeyWithRSA(payload.encryptedAesKey, privateKey)
// ๋ฉ์ธ์ง ๋ณตํธํ(๋์นญํค)
val plainText = encryptionService.decryptAESWithIVPrefixed(payload.encryptedMessage, aesKey)
MessageResponse("${payload.senderId}: $plainText")
}
}
}
- sendMessage`: ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ด๊ธฐ ์ํ API
- ๋์นญํค๋ฅผ ์ฌ์ฉํด์ ๋ฉ์ธ์ง๋ฅผ ์ํธํํ๋ค.
- ์ฌ์ฉ๋ ๋์นญํค๋ฅผ ๊ฐ์ด ๋ณด๋ด์ผํ๊ธฐ ๋๋ฌธ์ ์ด ๋์นญํค๋ฅผ ์์ ์์ ๊ณต๊ฐํค๋ก ์ํธํ ํ๋ค.
- ํด๋น ๋ฉ์ธ์ง๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์์ ์ ์ฅํด๋๋ค.
receiveMessages
: ์์ ๋ ๋ฉ์ธ์ง ํ์ธ์ ์ํ API- ๋์ ๋น๊ณต๊ฐํค๋ฅผ ์ฌ์ฉํ์ฌ ๋์นญํค๋ฅผ ์ฐ์ ๋ณตํธํ ํ๋ค.
- ๋ณตํธํ๋ ํค๋ก ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํํ๋ค.
๐ ์ ์ฒด ์ฝ๋๋ ์๋์์! ๐
GitHub - jeongum/e2ee
Contribute to jeongum/e2ee development by creating an account on GitHub.
github.com
'๐ป ๊ฐ๋ฐ ์ผ์ง > SpringBoot' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ํธํ์ ๋ ์ถ: ๋์นญํค vs ๋น๋์นญํค
์ํธํ ์์คํ ์ ํฌ๊ฒ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ๋๋๋ค.
- ๋์นญํค ์ํธํ: ํ๋์ ๋น๋ฐํค๋ก ์ํธํ์ ๋ณตํธํ๋ฅผ ๋ชจ๋ ์ํ. ๋ํ์ ์ผ๋ก AES. ๋น ๋ฅด๊ณ ๊ณ์ฐ๋์ด ์ ์ด ๋์ฉ๋ ๋ฐ์ดํฐ ์ํธํ์ ์ ํฉ.
- ๋น๋์นญํค ์ํธํ: ๊ณต๊ฐํค์ ๋น๊ณต๊ฐํค๋ผ๋ ๋ ๊ฐ์ ํค๋ฅผ ์ฌ์ฉ. ๋ํ์ ์ผ๋ก RSA๊ฐ ์์ผ๋ฉฐ, ํ ํค๋ก ์ํธํํ ๋ฐ์ดํฐ๋ ๋ฐ๋์ ๋ค๋ฅธ ํค๋ก๋ง ๋ณตํธํ ๊ฐ๋ฅ. ํค ๊ตํ, ์ธ์ฆ, ์๋ช ๋ฑ์ ์ฌ์ฉ
โก ์ฑ๋ฅ ์ธก๋ฉด์์์ ์ฐจ์ด
ํญ๋ชฉ ๋์นญํค (AES) ๋น๋์นญํค (RSA)
์๋ | ๋งค์ฐ ๋น ๋ฆ | ์๋์ ์ผ๋ก ๋๋ฆผ |
์ฐ์ฐ๋ | ์ ์ | ๋ง์ (ํฐ ์ ์ฐ์ฐ) |
์์ ์ฌ์ฉ | ๋ฎ์ | ๋์ |
์ฌ์ฉ ์ฉ๋ | ๋ฉ์์ง/ํ์ผ ์ํธํ | ํค ์ ๋ฌ/์๋ช /์ธ์ฆ |
์ด๋ฌํ ์ฑ๋ฅ ์ฐจ์ด๋ก ์ธํด ์ค์ ๋ณด์ ์์คํ ์์๋ โํ์ด๋ธ๋ฆฌ๋ ์ํธํโ๋ผ๋ ๋ฐฉ์์ด ๋ฑ์ฅ
ํ์ด๋ธ๋ฆฌ๋ ์ํธํ๋?
๋น ๋ฅด๊ณ ํจ์จ์ ์ธ AES๋ก ๋ฉ์์ง๋ฅผ ์ํธํํ๊ณ , ์ด AES ํค๋ฅผ ์์ ์์ ๊ณต๊ฐํค(RSA)๋ก ์ํธํํ๋ ๋ฐฉ์
์ ์ก ํ๋ฆ:
- ํด๋ผ์ด์ธํธ๋ ๋๋ค AES ํค๋ฅผ ์์ฑ
- ๋ฉ์์ง๋ฅผ AES ํค๋ก ์ํธํ (IV ํฌํจ)
- AES ํค๋ฅผ ์๋๋ฐฉ์ ๊ณต๊ฐํค๋ก RSA ์ํธํ
- ์ํธ๋ฌธ + ์ํธํ๋ AES ํค๋ฅผ ์๋ฒ์ ์ ์ฅ ๋๋ ์ ์ก
์์ ์๋:
- ์์ ์ ๋น๊ณต๊ฐํค๋ก AES ํค๋ฅผ ๋ณตํธํ
- ํด๋น AES ํค๋ก ๋ฉ์์ง๋ฅผ ๋ณตํธํ
์ด ๊ตฌ์กฐ๋ฅผ ํตํด ์๋์ ๋ณด์์ ๋์์ ํ๋ณด
IV๋? (Initialization Vector): AES ๋ธ๋ก ์ํธํ์์ ์ฒซ ๋ธ๋ก์ ๋๋คํํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ 16๋ฐ์ดํธ ๊ฐ. ์ํธ๋ฌธ์ ํฌํจ๋๋ฉฐ ๊ณต๊ฐ๋์ด๋ ๋ณด์์ ๋ฌธ์ ๊ฐ ์์.
์ค์ ๊ตฌํ ์ ์ํธ๋ฌธ ๊ตฌ์กฐ
{
"encryptedMessage": "<IV + AES ์ํธ๋ฌธ>",
"encryptedAesKey": "<RSA๋ก ์ํธํ๋ AES ํค>"
}
์ ํด๋ผ์ด์ธํธ์์ ์๋ณตํธํ๋ฅผ ํ ๊น?
- ์๋ฒ์์ ์๋ณตํธํ๋ฅผ ํ๋ฉด ์๋ฒ๊ฐ ํ๋ฌธ์ ๋ณด๊ฒ ๋๋ฏ๋ก ์ง์ ํ ๋ณด์์ด ์ด๋ ค์
- ํด๋ผ์ด์ธํธ์์ ํค๋ฅผ ์์ฑํ๊ณ , ์ํธํ๋ฅผ ์ํํ๋ฉฐ ์๋ฒ๋ ๋จ์ํ ์ ๋ฌ์ ์ญํ ๋ง ์ํํ๋ฉด ์ง์ ํ ์ข ๋จ๊ฐ ์ํธํ (E2EE) ๊ตฌ์กฐ๊ฐ ์์ฑ.
ํด๋ผ์ด์ธํธ ์ค์ฌ E2EE ํ๋ฆ ์ ๋ฆฌ
- ํด๋ผ์ด์ธํธ๊ฐ ๊ณต๊ฐํค๋ฅผ ์๋ฒ์ ๋ฑ๋ก
- ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ธฐ ์ ์๋๋ฐฉ์ ๊ณต๊ฐํค๋ฅผ ์๋ฒ์์ ์กฐํ
- AES ํค ์์ฑ โ ๋ฉ์์ง๋ฅผ ์ํธํ โ AES ํค๋ ์๋์ ๊ณต๊ฐํค๋ก ์ํธํ
- ์์ ์๋ ์์ ์ ๋น๊ณต๊ฐํค๋ก AES ํค ๋ณตํธํ โ ๋ฉ์์ง ๋ณตํธํ
โ ๊ฒฐ๋ก
- ๋์นญํค๋ ๋น ๋ฅด์ง๋ง ํค ์ ๋ฌ์ด ๋ฌธ์
- ๋น๋์นญํค๋ ๋๋ฆฌ์ง๋ง ํค ์ ๋ฌ์ ๊ฐ๋ ฅํจ
- ๊ทธ๋์ ํ์ด๋ธ๋ฆฌ๋ ์ํธํ๋ฅผ ํตํด ๋์ ์ฅ์ ์ ์กฐํฉ
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ณตํธํ๋ฅผ ์ฃผ๋ํ๋ฉด ์ง์ ํ ์ข ๋จ๊ฐ ๋ณด์(E2EE)์ด ๊ฐ๋ฅํจ
์ฝ๋๋ก ์ดํดํ๊ธฐ
๐กํด๋ผ์ด์ธํธ์ ์ญํ ์ด ๋๋ฌด๋๋ ๋ช ํํ ์ฃผ์ ๋ผ ํด๋ผ์ด์ธํธ๋ฅผ ํจ๊ป ๊ตฌํํ๊ณ ์ถ์์ง๋ง, ๋น ๋ฅธ ์ดํด์ ํ์ต์ ์ํด SpringBoot๋ก E2EE ๋ชจ๋ Flow๋ฅผ ๊ตฌํํ์๋ค.
์ฝ๋๋ฅผ ๊ตฌํํ๋ฉด์ ์๊ฒ๋ ์๋ก์ด ์ฌ์ค์ด ์๋ค.
์ผ๋ฐ ํด๋ผ-์๋ฒ ๊ด๊ณ๋ผ๋ฉด ์๋ฒ๋ ๋จ์ํ ํด๋ผ์์ ์ ์กํ publicKey๋ง ๋ค๊ณ ์์ผ๋ฉฐ, keyPair์์ฑ, ๋ฉ์ธ์ง ์/๋ณตํธํ์ ๋กค์ ๋ชจ๋ ํด๋ผ๊ฐ ๊ฐ๋๋ค. ๋ํ, ๋น๋์นญํค ์/๋ณตํธํ๋ ๋ง์ computing ์์์ด ํ์ํ๊ธฐ ๋๋ฌธ์, ๋น๋์นญํค๋ก ์ํธํ๋ ๋์นญํค๋ฅผ ๋งค๋ฒ ๋ณ๊ฒฝํ์ง ์๋๋ค.
์ฆ, ํด๋ผ์์ ๋น๋์นญํค๋ก ๋ณตํธํํ ๋ฉ์ธ์ง ๋์นญํค๋ฅผ Caching ํด๋๊ณ , ๋น ๋ฅด๊ฒ ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํ ํ ์ ์๋๋ก ํ๋ค.
Service
@Service
class EncryptionService {
/**
* AES: ๋์นญํค ๋ธ๋ก ์ํธํ ์๊ณ ๋ฆฌ์ฆ
* iv: ๋์ผํ ์ํธํ ๊ฒฐ๊ณผ๊ฐ ๋์ค์ง ์๋๋ก ๋ถ์ฌ์ฃผ๋ ๋๋ค ๊ฐ (ํค๋ ๋ฌด๊ด)
*/
// AES ๋์นญํค ์์ฑ: 256 ๋นํธ ๊ธธ์ด (๋ฉ์ธ์ง ๋ณธ๋ฌธ ์ํธํ ํ๋๋ฐ ์ฌ์ฉ)
fun generateAESKey(): SecretKey {
val keyGen = KeyGenerator.getInstance("AES")
keyGen.init(256)
return keyGen.generateKey()
}
// ํ๋ฌธ ๋ฉ์ธ์ง ์ํธํ: ๋์นญํค(secretKey) ์ฌ์ฉ
fun encryptAESWithIVPrefixed(plainText: String, secretKey: SecretKey): String {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์์ฑ
val ivBytes = ByteArray(16) // 16 ๋ฐ์ดํธ ์ง๋ฆฌ ๋ฐฐ์ด ์์ฑ
SecureRandom().nextBytes(ivBytes) // ivBytes ๋ฐฐ์ด์ ๋ฌด์์ ๊ฐ ์ฑ์
val ivSpec = IvParameterSpec(ivBytes)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec) // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์ด๊ธฐํ
val encryptedBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))
val combined = ivBytes + encryptedBytes // ๋ฉ์ธ์ง ์์ 16 ๋ฐ์ดํธ๋ฅผ ๋ถ์ฌ์, ๋ฉ์ธ์ง ์์ฑ
return Base64.getEncoder().encodeToString(combined)
}
// ๋ฉ์ ์ง ๋ณตํธํ: ๋์นญํค(SecretKey) ์ฌ์ฉ
fun decryptAESWithIVPrefixed(encryptedCombined: String, secretKey: SecretKey): String {
val combinedBytes = Base64.getDecoder().decode(encryptedCombined)
val iv = combinedBytes.copyOfRange(0, 16) // ์ํธํ์ ์ฌ์ฉํ iv ์ถ์ถ
val encrypted = combinedBytes.copyOfRange(16, combinedBytes.size) // ์ํธํ๋ ์ค์ ๋ฉ์ธ์ง ์ถ์ถ
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์์ฑ
val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec) // ์ํธ ์ฒ๋ฆฌ๊ธฐ ์ด๊ธฐํ - Decrypt ๋ชจ๋
val decryptedBytes = cipher.doFinal(encrypted)
return String(decryptedBytes, Charsets.UTF_8)
}
// ๋์นญํค ์ํธํ: ์์ ์์ ๊ณต๊ฐํค๋ก ์ํธํ ํ์ฌ ์ ๋ฌํ ์ ์๋๋ก ํจ
fun encryptAESKeyWithRSA(secretKey: SecretKey, publicKey: PublicKey): String {
val cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val encryptedKey = cipher.doFinal(secretKey.encoded)
return Base64.getEncoder().encodeToString(encryptedKey)
}
// ๋์นญํค ๋ณตํธํ: ๋น๊ณต๊ฐํค๋ก ๋ณตํธํ
fun decryptAESKeyWithRSA(encryptedAesKey: String, privateKey: PrivateKey): SecretKey {
val cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.DECRYPT_MODE, privateKey)
val decodedKey = cipher.doFinal(Base64.getDecoder().decode(encryptedAesKey))
return SecretKeySpec(decodedKey, "AES")
}
}
generateAESKey
: ๋ฉ์ธ์ง๋ฅผ ์ํธํํ๊ธฐ ์ํ ๋๋ค ํค๋ฅผ ์์ฑencryptAESKeyWithRSA
/decryptAESKeyWithRSA
: ๋์นญํค ์/๋ณตํธํ ์ฝ๋- ์ํธํ: ์์ ์์ ๊ณต๊ฐํค๋ก ๋์นญํค๋ฅผ ์ํธํํ๋ค.
- ๋ณตํธํ: ๋ณธ์ธ์ ๋น๊ณต๊ฐํค๋ก ๋์นญํค๋ฅผ ๋ณตํธํํ๋ค.
encryptAESWithIVPrefixed
/decryptAESWithIVPrefixed
: ๋ฉ์ธ์ง ์/๋ณตํธํ ์ฝ๋- ์ํธํ: ๋์นญํค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์ธ์ง๋ฅผ ์ํธํํ๋ค. ์ด๋ ๋ฉ์ธ์ง ์์ iv๋ฅผ ๋ถ์ฌ์, ๊ฐ์ ๋ด์ฉ์ ๋ํด ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋์ค์ง ์๋๋ก ํ๋ค.
- ๋ณตํธํ: ๋์นญํค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํํ๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก iv bytes๋ฅผ ์ ๊ฑฐํ๊ณ , ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํ ํ๋ค.
Controller
@RequestMapping("/api")
class MessageController(
private val encryptionService: EncryptionService,
) {
private val publicKeyMap: MutableMap<String, PublicKey> = mutableMapOf()
private val privateKeyMap: MutableMap<String, PrivateKey> = mutableMapOf()
private val messageBox: MutableMap<String, MutableList<EncryptedPayload>> = mutableMapOf()
@PostMapping("/register")
fun registerUser(@RequestBody request: PublicKeyRegisterRequest): String {
// userId์ ๋ํ KeyPair ์์ฑ
val keyPairGen = KeyPairGenerator.getInstance("RSA")
keyPairGen.initialize(2048)
val keyPair = keyPairGen.generateKeyPair()
publicKeyMap[request.userId] = keyPair.public
privateKeyMap[request.userId] = keyPair.private
return "User ${request.userId} registered with key pair"
}
@PostMapping("/send")
fun sendMessage(@RequestBody request: MessageRequest): String {
val receiverPublicKey = publicKeyMap[request.receiverId] ?: error("Receiver not found")
val aesKey = encryptionService.generateAESKey()
// ๋ฉ์ธ์ง ์ํธํ(๋์นญํค)
val encryptedMessage = encryptionService.encryptAESWithIVPrefixed(request.message, aesKey)
// ๋์นญํค ์ํธํ(๋น๋์นญํค)
val encryptedAesKey = encryptionService.encryptAESKeyWithRSA(aesKey, receiverPublicKey)
val payload = EncryptedPayload(request.senderId, encryptedMessage, encryptedAesKey)
messageBox.computeIfAbsent(request.receiverId) { mutableListOf() }.add(payload)
return "Message sent securely to ${request.receiverId}"
}
@GetMapping("/messages/{userId}")
fun receiveMessages(@PathVariable userId: String): List<MessageResponse> {
val privateKey = privateKeyMap[userId] ?: error("Private key not found for $userId")
val messages = messageBox[userId] ?: return emptyList()
return messages.map { payload ->
// ๋์นญํค ๋ณตํธํ(๋น๋์นญํค)
val aesKey = encryptionService.decryptAESKeyWithRSA(payload.encryptedAesKey, privateKey)
// ๋ฉ์ธ์ง ๋ณตํธํ(๋์นญํค)
val plainText = encryptionService.decryptAESWithIVPrefixed(payload.encryptedMessage, aesKey)
MessageResponse("${payload.senderId}: $plainText")
}
}
}
- sendMessage`: ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ด๊ธฐ ์ํ API
- ๋์นญํค๋ฅผ ์ฌ์ฉํด์ ๋ฉ์ธ์ง๋ฅผ ์ํธํํ๋ค.
- ์ฌ์ฉ๋ ๋์นญํค๋ฅผ ๊ฐ์ด ๋ณด๋ด์ผํ๊ธฐ ๋๋ฌธ์ ์ด ๋์นญํค๋ฅผ ์์ ์์ ๊ณต๊ฐํค๋ก ์ํธํ ํ๋ค.
- ํด๋น ๋ฉ์ธ์ง๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์์ ์ ์ฅํด๋๋ค.
receiveMessages
: ์์ ๋ ๋ฉ์ธ์ง ํ์ธ์ ์ํ API- ๋์ ๋น๊ณต๊ฐํค๋ฅผ ์ฌ์ฉํ์ฌ ๋์นญํค๋ฅผ ์ฐ์ ๋ณตํธํ ํ๋ค.
- ๋ณตํธํ๋ ํค๋ก ๋ฉ์ธ์ง๋ฅผ ๋ณตํธํํ๋ค.
๐ ์ ์ฒด ์ฝ๋๋ ์๋์์! ๐
GitHub - jeongum/e2ee
Contribute to jeongum/e2ee development by creating an account on GitHub.
github.com