๐Ÿ’ป ๊ฐœ๋ฐœ ์ผ์ง€/SpringBoot

[SpringBoot/Kotlin] E2EE: ๊ฐ„๋‹จํ•œ ๋ฉ”์‹ ์ € ์„œ๋ฒ„ ๊ตฌํ˜„ํ•˜๊ธฐ

์ ์ด 2025. 4. 12. 15:56
๋ฐ˜์‘ํ˜•

์•”ํ˜ธํ™”์˜ ๋‘ ์ถ•: ๋Œ€์นญํ‚ค vs ๋น„๋Œ€์นญํ‚ค

์•”ํ˜ธํ™” ์‹œ์Šคํ…œ์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๋‚˜๋‰œ๋‹ค.

  • ๋Œ€์นญํ‚ค ์•”ํ˜ธํ™”: ํ•˜๋‚˜์˜ ๋น„๋ฐ€ํ‚ค๋กœ ์•”ํ˜ธํ™”์™€ ๋ณตํ˜ธํ™”๋ฅผ ๋ชจ๋‘ ์ˆ˜ํ–‰. ๋Œ€ํ‘œ์ ์œผ๋กœ AES. ๋น ๋ฅด๊ณ  ๊ณ„์‚ฐ๋Ÿ‰์ด ์ ์–ด ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”์— ์ ํ•ฉ.
  • ๋น„๋Œ€์นญํ‚ค ์•”ํ˜ธํ™”: ๊ณต๊ฐœํ‚ค์™€ ๋น„๊ณต๊ฐœํ‚ค๋ผ๋Š” ๋‘ ๊ฐœ์˜ ํ‚ค๋ฅผ ์‚ฌ์šฉ. ๋Œ€ํ‘œ์ ์œผ๋กœ RSA๊ฐ€ ์žˆ์œผ๋ฉฐ, ํ•œ ํ‚ค๋กœ ์•”ํ˜ธํ™”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ ๋‹ค๋ฅธ ํ‚ค๋กœ๋งŒ ๋ณตํ˜ธํ™” ๊ฐ€๋Šฅ. ํ‚ค ๊ตํ™˜, ์ธ์ฆ, ์„œ๋ช… ๋“ฑ์— ์‚ฌ์šฉ

โšก ์„ฑ๋Šฅ ์ธก๋ฉด์—์„œ์˜ ์ฐจ์ด

ํ•ญ๋ชฉ ๋Œ€์นญํ‚ค (AES) ๋น„๋Œ€์นญํ‚ค (RSA)

์†๋„ ๋งค์šฐ ๋น ๋ฆ„ ์ƒ๋Œ€์ ์œผ๋กœ ๋А๋ฆผ
์—ฐ์‚ฐ๋Ÿ‰ ์ ์Œ ๋งŽ์Œ (ํฐ ์ˆ˜ ์—ฐ์‚ฐ)
์ž์› ์‚ฌ์šฉ ๋‚ฎ์Œ ๋†’์Œ
์‚ฌ์šฉ ์šฉ๋„ ๋ฉ”์‹œ์ง€/ํŒŒ์ผ ์•”ํ˜ธํ™” ํ‚ค ์ „๋‹ฌ/์„œ๋ช…/์ธ์ฆ

์ด๋Ÿฌํ•œ ์„ฑ๋Šฅ ์ฐจ์ด๋กœ ์ธํ•ด ์‹ค์ œ ๋ณด์•ˆ ์‹œ์Šคํ…œ์—์„œ๋Š” “ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™””๋ผ๋Š” ๋ฐฉ์‹์ด ๋“ฑ์žฅ

 ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™”๋ž€?

๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ธ AES๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์•”ํ˜ธํ™”ํ•˜๊ณ , ์ด AES ํ‚ค๋ฅผ ์ˆ˜์‹ ์ž์˜ ๊ณต๊ฐœํ‚ค(RSA)๋กœ ์•”ํ˜ธํ™”ํ•˜๋Š” ๋ฐฉ์‹

์ „์†ก ํ๋ฆ„:

  1. ํด๋ผ์ด์–ธํŠธ๋Š” ๋žœ๋ค AES ํ‚ค๋ฅผ ์ƒ์„ฑ
  2. ๋ฉ”์‹œ์ง€๋ฅผ AES ํ‚ค๋กœ ์•”ํ˜ธํ™” (IV ํฌํ•จ)
  3. AES ํ‚ค๋ฅผ ์ƒ๋Œ€๋ฐฉ์˜ ๊ณต๊ฐœํ‚ค๋กœ RSA ์•”ํ˜ธํ™”
  4. ์•”ํ˜ธ๋ฌธ + ์•”ํ˜ธํ™”๋œ AES ํ‚ค๋ฅผ ์„œ๋ฒ„์— ์ €์žฅ ๋˜๋Š” ์ „์†ก

์ˆ˜์‹ ์ž๋Š”:

  1. ์ž์‹ ์˜ ๋น„๊ณต๊ฐœํ‚ค๋กœ AES ํ‚ค๋ฅผ ๋ณตํ˜ธํ™”
  2. ํ•ด๋‹น AES ํ‚ค๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณตํ˜ธํ™”

์ด ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด ์†๋„์™€ ๋ณด์•ˆ์„ ๋™์‹œ์— ํ™•๋ณด

 IV๋ž€? (Initialization Vector): AES ๋ธ”๋ก ์•”ํ˜ธํ™”์—์„œ ์ฒซ ๋ธ”๋ก์„ ๋žœ๋คํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” 16๋ฐ”์ดํŠธ ๊ฐ’. ์•”ํ˜ธ๋ฌธ์— ํฌํ•จ๋˜๋ฉฐ ๊ณต๊ฐœ๋˜์–ด๋„ ๋ณด์•ˆ์— ๋ฌธ์ œ๊ฐ€ ์—†์Œ.

 

 ์‹ค์ œ ๊ตฌํ˜„ ์‹œ ์•”ํ˜ธ๋ฌธ ๊ตฌ์กฐ

{
  "encryptedMessage": "<IV + AES ์•”ํ˜ธ๋ฌธ>",
  "encryptedAesKey": "<RSA๋กœ ์•”ํ˜ธํ™”๋œ AES ํ‚ค>"
}

 

์™œ ํด๋ผ์ด์–ธํŠธ์—์„œ ์•”๋ณตํ˜ธํ™”๋ฅผ ํ• ๊นŒ?

  • ์„œ๋ฒ„์—์„œ ์•”๋ณตํ˜ธํ™”๋ฅผ ํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ํ‰๋ฌธ์„ ๋ณด๊ฒŒ ๋˜๋ฏ€๋กœ ์ง„์ •ํ•œ ๋ณด์•ˆ์ด ์–ด๋ ค์›€
  • ํด๋ผ์ด์–ธํŠธ์—์„œ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์•”ํ˜ธํ™”๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉฐ ์„œ๋ฒ„๋Š” ๋‹จ์ˆœํ•œ ์ „๋‹ฌ์ž ์—ญํ• ๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฉด ์ง„์ •ํ•œ ์ข…๋‹จ๊ฐ„ ์•”ํ˜ธํ™” (E2EE) ๊ตฌ์กฐ๊ฐ€ ์™„์„ฑ.

ํด๋ผ์ด์–ธํŠธ ์ค‘์‹ฌ E2EE ํ๋ฆ„ ์ •๋ฆฌ

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ณต๊ฐœํ‚ค๋ฅผ ์„œ๋ฒ„์— ๋“ฑ๋ก
  2. ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๊ธฐ ์ „ ์ƒ๋Œ€๋ฐฉ์˜ ๊ณต๊ฐœํ‚ค๋ฅผ ์„œ๋ฒ„์—์„œ ์กฐํšŒ
  3. AES ํ‚ค ์ƒ์„ฑ → ๋ฉ”์‹œ์ง€๋ฅผ ์•”ํ˜ธํ™” → AES ํ‚ค๋Š” ์ƒ๋Œ€์˜ ๊ณต๊ฐœํ‚ค๋กœ ์•”ํ˜ธํ™”
  4. ์ˆ˜์‹ ์ž๋Š” ์ž์‹ ์˜ ๋น„๊ณต๊ฐœํ‚ค๋กœ 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")
    }
}

 

  1. `generateAESKey`: ๋ฉ”์„ธ์ง€๋ฅผ ์•”ํ˜ธํ™”ํ•˜๊ธฐ ์œ„ํ•œ ๋žœ๋ค ํ‚ค๋ฅผ ์ƒ์„ฑ
  2. `encryptAESKeyWithRSA` / `decryptAESKeyWithRSA`: ๋Œ€์นญํ‚ค ์•”/๋ณตํ˜ธํ™” ์ฝ”๋“œ
    1. ์•”ํ˜ธํ™”: ์ˆ˜์‹ ์ž์˜ ๊ณต๊ฐœํ‚ค๋กœ ๋Œ€์นญํ‚ค๋ฅผ ์•”ํ˜ธํ™”ํ•œ๋‹ค.
    2. ๋ณตํ˜ธํ™”: ๋ณธ์ธ์˜ ๋น„๊ณต๊ฐœํ‚ค๋กœ ๋Œ€์นญํ‚ค๋ฅผ ๋ณตํ˜ธํ™”ํ•œ๋‹ค. 
  3. `encryptAESWithIVPrefixed` / `decryptAESWithIVPrefixed`: ๋ฉ”์„ธ์ง€ ์•”/๋ณตํ˜ธํ™” ์ฝ”๋“œ
    1. ์•”ํ˜ธํ™”: ๋Œ€์นญํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์„ธ์ง€๋ฅผ ์•”ํ˜ธํ™”ํ•œ๋‹ค. ์ด๋•Œ ๋ฉ”์„ธ์ง€ ์•ž์— iv๋ฅผ ๋ถ™์—ฌ์„œ, ๊ฐ™์€ ๋‚ด์šฉ์— ๋Œ€ํ•ด ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
    2. ๋ณตํ˜ธํ™”: ๋Œ€์นญํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณตํ˜ธํ™”ํ•œ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ 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")
        }
    }
}

 

  1. sendMessage`: ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ด๊ธฐ ์œ„ํ•œ API
    1. ๋Œ€์นญํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฉ”์„ธ์ง€๋ฅผ ์•”ํ˜ธํ™”ํ•œ๋‹ค.
    2. ์‚ฌ์šฉ๋œ ๋Œ€์นญํ‚ค๋ฅผ ๊ฐ™์ด ๋ณด๋‚ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋Œ€์นญํ‚ค๋ฅผ ์ˆ˜์‹ ์ž์˜ ๊ณต๊ฐœํ‚ค๋กœ ์•”ํ˜ธํ™” ํ•œ๋‹ค.
    3. ํ•ด๋‹น ๋ฉ”์„ธ์ง€๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์ž„์‹œ ์ €์žฅํ•ด๋‘”๋‹ค.
  2. `receiveMessages`: ์ˆ˜์‹ ๋œ ๋ฉ”์„ธ์ง€ ํ™•์ธ์„ ์œ„ํ•œ API
    1. ๋‚˜์˜ ๋น„๊ณต๊ฐœํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€์นญํ‚ค๋ฅผ ์šฐ์„  ๋ณตํ˜ธํ™” ํ•œ๋‹ค.
    2. ๋ณตํ˜ธํ™”๋œ ํ‚ค๋กœ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณตํ˜ธํ™”ํ•œ๋‹ค.

 

๐Ÿ‘‰ ์ „์ฒด ์ฝ”๋“œ๋Š” ์•„๋ž˜์—์„œ! ๐Ÿ‘ˆ

 

GitHub - jeongum/e2ee

Contribute to jeongum/e2ee development by creating an account on GitHub.

github.com

 

๋ฐ˜์‘ํ˜•