header-img
Info :

์šฉ์–ด ์ •๋ฆฌ

  • key : ํ•ด์‹ฑ๋˜๊ธฐ ์ „์˜ ๊ฐ’
  • digest : ํ•ด์‹ฑ๋œ ํ›„์˜ ๊ฐ’
  • rainbow table : ์—ฌ๋Ÿฌ ๊ฐ’๋“ค์„ ๋Œ€์ž…ํ•ด๋ณด๋ฉด์„œ ์–ป์€ ๋‹ค์ด์ œ์ŠคํŠธ๋“ค์„ ๋ชจ์•„๋†“์€ ํ…Œ์ด๋ธ”

 

ํ•ด์‹œ ํ•จ์ˆ˜

  • ๋‹จ๋ฐฉํ–ฅ ํ•จ์ˆ˜๋กœ๋งŒ ์ž‘๋™ํ•จ
  • input ๊ฐ’์ด ์•„์ฃผ ๋ฏธ์„ธํ•˜๊ฒŒ ๋‹ฌ๋ผ์ ธ๋„ output ๊ฐ’์€ ์ „ํ˜€ ๋‹ฌ๋ผ์ง → Avalanche Effect
  • ํ•ด์‹œ ํ•จ์ˆ˜์˜ ์›๋ž˜ ์„ค๊ณ„ ๋ชฉ์ ์€ ๋น ๋ฅธ ๊ฒ€์ƒ‰์„ ์œ„ํ•จ์ž„ (OS ๋ถ€๋ถ„ ํ•ด์‹œํ…Œ์ด๋ธ” ์ฐธ๊ณ )
  • input ๊ฐ’์ด ๊ฐ™์œผ๋ฉด output ๊ฐ’์€ ํ•ญ์ƒ ๊ฐ™์Œ (์ฆ‰ ํ•จ์ˆ˜๋กœ์„œ ๊ธฐ๋Šฅํ•จ)

 

ํ•ด์‹œ ํ•จ์ˆ˜์˜ ์œ„ํ—˜์„ฑ

  • input์ด ๊ฐ™์œผ๋ฉด output์€ ํ•ญ์ƒ ๊ฐ™์œผ๋ฏ€๋กœ ํ•ด์‹ฑ๋œ ๋ฌธ์ž์—ด์˜ ์›๋ฌธ์„ ๋ ˆ์ธ๋ณด์šฐ ํ…Œ์ด๋ธ”์—์„œ ์ฐพ์„ ์œ„ํ—˜์„ฑ์ด ์กด์žฌํ•จ.
  • brute-force
    • ์›๋ž˜ ํ•ด์‹œ ํ•จ์ˆ˜๊ฐ€ ๋น ๋ฅธ ๊ฒ€์ƒ‰์„ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ๋‹ค ๋ณด๋‹ˆ ์‹ค์ œ ๋Ÿฐํƒ€์ž„์—์„œ์˜ ์†๋„๋„ ๋น ๋ฅธ ๊ฑด ๋งž์Œ. ํ•˜์ง€๋งŒ ๊ทธ๋ ‡๋‹ค๋ฉด ํ•ด์ปค๋„ ๋˜‘๊ฐ™์ด ๋น ๋ฅด๊ฒŒ ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Œ.

 

์œ„ํ—˜์„ฑ ๋ณด์™„ ๋ฐฉ๋ฒ•

  • salt
    • ํšจ๊ณผ์ ์ธ ์†”ํŠธ : ์‚ฌ์šฉ์ž๋ณ„ ๊ณ ์œ ์˜ ์†”ํŠธ๋ฅผ ๊ฐ€์ ธ์•ผ ํ•˜๋ฉฐ ์†”ํŠธ์˜ ๊ธธ์ด๋Š” 32๋น„ํŠธ ์ด์ƒ์ด์–ด์•ผ ํ•จ
  • key stretching
    • ์ž…๋ ฅ๊ฐ’ -ํ•ด์‹ฑ→ ๋‹ค์ด์ œ์ŠคํŠธ -ํ•ด์‹ฑ→ ๋‹ค์ด์ œ์ŠคํŠธ’ -ํ•ด์‹ฑ→ ๋‹ค์ด์ œ์ŠคํŠธ’’ ๋ฐฉ์‹์œผ๋กœ ์—ฌ๋Ÿฌ๋ฒˆ ํ•ด์‹ฑํ•˜๋Š” ๊ฒƒ

 

ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ข…๋ฅ˜

MD5 ๊ณ„์—ด

  • ๊ฐ€์žฅ ์‰ฝ๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์•ˆ์ „ํ•˜์ง€ ์•Š์Œ
  • ํ•ด์‹œ๊ฐ’์ด ๋‹จ์ˆœํ•˜๊ฒŒ ๋ฐ”๋€Œ๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ตฌ๊ธ€๋ง์œผ๋กœ ์›๋ณธ ๊ฐ’์„ ์ฐพ๊ธฐ ์‰ฌ์›€
  • ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋จ

SHA ๊ณ„์—ด

  • SHA-0 : ์ˆ˜์—†์ด ๋งŽ์ด ๋…ธ์ถœ๋จ. ๋ณด์•ˆ์ด ๊ฑฐ์˜ ์ ์šฉ๋˜์ง€ ์•Š์Œ. ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ๊ถŒ์žฅ.
  • SHA-1 : SHA-0์—์„œ ๊ฐœ์„ ๋œ ๋ฒ„์ „
  • SHA-2 : SHA-256์ด ์—ฌ๊ธฐ ํฌํ•จ๋จ
  • SHA-3 : SHA-384๊ฐ€ ์—ฌ๊ธฐ ํฌํ•จ๋จ

 

Key Derivation Functions

  • ํ‚ค ์œ ๋„ ํ•จ์ˆ˜๋Š” ์†”ํŒ…, ํ‚ค ์ŠคํŠธ๋ ˆ์นญ์„ ์‚ฌ์šฉํ•ด ๊ธฐ์กด ํ•ด์‹œ ํ•จ์ˆ˜์˜ ์•ฝ์  ๋ณด์™„
  • PBKDF2
    • ํ‚ค ์œ ๋„ ํ•จ์ˆ˜ ์ค‘ ํ•˜๋‚˜๋กœ ํ‚ค ๊ฐ’์— ์†”ํŠธ๋ฅผ ๋„ฃ๊ณ  ์ด๋ฅผ ์›ํ•˜๋Š” ๋งŒํผ ํ‚ค์ŠคํŠธ๋ ˆ์นญํ•˜๋Š” ๋ฐฉ์‹
    • DIGEST = PBKDF2(PRF, Password, Salt, c, DLen)
      • PRF: ๋‚œ์ˆ˜ (HMAC)
      • Password: key ๊ฐ’
      • c: ํ‚ค ์ŠคํŠธ๋ ˆ์นญ์„ ์›ํ•˜๋Š” ํšŸ์ˆ˜
      • DLen: ์›ํ•˜๋Š” ๋‹ค์ด์ œ์ŠคํŠธ ๊ธธ์ด
  • Scrypt
    • ํ‚ค ์œ ๋„ ํ•จ์ˆ˜ ์ค‘ ํ•˜๋‚˜๋กœ ๋‹ค์ด์ œ์ŠคํŠธ ์ƒ์„ฑ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฒ„ํ—คํŠธ๋ฅผ ๊ฐ–๊ฒŒ ํ•˜์—ฌ brute force ๊ณต๊ฒฉ ๊ฐ€๋Šฅ์„ฑ์„ ๋ณด์™„ํ•จ.

 

์ธ์ฝ”๋”ฉ ๋ฆฌ์„œ์น˜

SHA-256์˜ ๊ฒฐ๊ณผ๋ฌผ : ๋ฌด์กฐ๊ฑด 256 bit ๋ฆฌํ„ด → 32 byte

base64 ์ธ์ฝ”๋”ฉ ์‹œ ๋ฐ”์ดํŠธ์˜ ๊ธธ์ด๊ฐ€ 3๋ฐฐ์ˆ˜์ผ ๋•Œ๋งŒ ํŒจ๋”ฉ์ด ๋“ค์–ด๊ฐ€์ง€ ์•Š์Œ.

Base64

SHA-256์˜ ๊ฒฐ๊ณผ๋ฌผ์˜ ๊ฒฝ์šฐ ๋งˆ์ง€๋ง‰ 2byte ์ธ์ฝ”๋”ฉ ๋ฐฉ๋ฒ•

๋งŒ์•ฝ SHA-256์„ ํ•œ ๊ฒฐ๊ณผ๋ฌผ์ด X๋ผ๊ณ  ํ•จ. X๋ฅผ Base64๋กœ ์ธ์ฝ”๋”ฉํ•  ๋•Œ ์•ž์˜ 30byte๋Š” 40๊ธ€์ž๋กœ ์ž˜ ์ธ์ฝ”๋”ฉ์ด ๋  ๊ฒƒ์ž„. ๊ทธ๋Ÿผ ๋‚จ์€ 2byte์˜ ์ธ์ฝ”๋”ฉ ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Œ. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‚จ์€ 2byte์˜ ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฐ’์ด 0101010101010101 ์ด๋ผ๊ณ  ๊ฐ€์ •.

๋งˆ์ง€๋ง‰ =์€ 0์ด์–ด์„œ =์ด ๋“ค์–ด๊ฐ„๊ฒŒ ์•„๋‹ˆ๊ณ  ํŒจ๋”ฉ๊ฐ’์œผ๋กœ ๋“ค์–ด๊ฐ„ ๋„์˜ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„ 0์ด๊ธฐ ๋•Œ๋ฌธ์— ํŒจ๋”ฉ์˜ ๊ฐœ๋…์œผ๋กœ ๋“ค์–ด๊ฐ„ =์ž„. ์–ด์จŒ๋“  SHA-256์„ Base64๋กœ ์ธ์ฝ”๋”ฉํ•œ ๊ฒฐ๊ณผ๋ฌผ์€ 44๊ธ€์ž์ธ๋ฐ ๋งˆ์ง€๋ง‰ ํ•œ ๊ธ€์ž๋Š” ๊ณ ์ •์ ์œผ๋กœ =์ž„. ๋”ฐ๋ผ์„œ ์—ฌ๊ธฐ์„œ ์ƒ๊ฐํ•ด๋ณธ ๋ฐฉ์•ˆ์ด ๋‘ ๊ฐ€์ง€์ž„.

  1. ๋งˆ์ง€๋ง‰ ๊ธ€์ž๋ฅผ =์ด ์•„๋‹Œ base64 ๋‚ด๋ถ€์˜ ๋žœ๋ค ๋‚œ์ˆ˜ ๊ฐ’์œผ๋กœ ๋งŒ๋“ค๊ณ  44๊ธ€์ž ๊ทธ๋Œ€๋กœ ์ „์†ก
  2. ๋งˆ์ง€๋ง‰ ๊ธ€์ž ์ž๋ฅด๊ณ  43๊ธ€์ž๋งŒ ์ „๋‹ฌ ํ…์ŠคํŠธ์— ๋ถ™์ด๊ธฐ

= ๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด ์œ„์˜ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ์›น์„œ๋ฒ„์˜ ๋™์ž‘์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Œ. ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š๋‹ค๊ณ  ํŒ๋‹จ.

  1. ์ „๋‹ฌ๋ฐ›์€ P.T๋ฅผ S.K๋ฅผ ์‚ฌ์šฉํ•ด์„œ A.T, C.T์œผ๋กœ decrypt
  2. ๋ณตํ˜ธํ™”ํ•œ ๊ฐ’ ์ค‘ C.T ๊ฐ’์— ๋ฏธ๋ฆฌ ์ •ํ•ด๋†“์€ salt๋ฅผ ๋„ฃ์€ ๊ฒƒ์„ SHA-256์œผ๋กœ ํ•ด์‹ฑํ•˜๋ฉด OTP๊ฐ€ ๋งŒ๋“ค์–ด์ง
  3. OTP๋ฅผ Base64๋กœ ์ธ์ฝ”๋”ฉํ•˜๋ฉด 44๊ธ€์ž๊ฐ€ ๋‚˜์˜ฌ ๊ฒƒ์ž„.
  4. ์ƒ์„ฑํ•œ OTP์˜ ์•ž 43 ๊ธ€์ž๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ ๋ฐ›์€ OTP์™€ ๋น„๊ตํ•˜๋ฉด ๋จ.

 

Salt ๋ฆฌ์„œ์น˜

์†”ํŠธ ๊ฐ’์„ ํƒ€์ž„ ๋ฒ ์ด์Šค์˜ ๋ฌด์–ธ๊ฐ€๋กœ ๋งŒ๋“ค๊ฑฐ์ž„. ๋งŒ๋“ค์–ด์ง„ ์†”ํŠธ ๊ฐ’์˜ interval๋ฅผ x์ดˆ๋ผ๊ณ  ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ƒ์„ฑ๋  ๊ฒƒ์ž„.

์‹œ๊ฐ„ Salt
0 ~ x - 1 salt0
x ~ 2x - 1 salt1
2x ~ 3x - 1 salt2
kx ~ (k + 1)x - 1 saltk

๋งŒ์•ฝ 10์ดˆ ๊ฐ„๊ฒฉ์ด๋ผ๊ณ  ํ•œ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ƒ์„ฑ๋  ๊ฒƒ์ž„.

์‹œ๊ฐ„(์ดˆ)  Salt
0 ~ 9 salt0
10 ~ 19 salt1
20 ~ 29 salt2
k * 10 ~ (k + 1) * 10 - 1 saltk

์ด๊ฒŒ ์ดˆ ๋‹จ์œ„๋กœ ํ•  ๋•Œ์˜ ์†”ํŠธ ๊ฐ’ ์ƒ์„ฑ์ž„. ๋‹จ ์ด๋ ‡๊ฒŒ ๋˜๋ฉด interval ๋™์•ˆ ๋งŒ๋“ค์–ด์ง€๋Š” api๋“ค์˜ OTP ๊ฐ’์€ ๊ฐ™๋‹ค๋Š”๊ฒŒ ๋‹จ์ ์ž„. ์–ด์จŒ๋“  api ๋™์ž‘์€ ๋Œ€์ฒด๋กœ 1์ดˆ ์•ˆ์— ์ด๋ค„์ง€๊ธฐ๋Š” ํ•˜๋Š”๋ฐ 1์ดˆ๋™์•ˆ ์ด๋ค„์ง€๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ api๋“ค์€ OTP๊ฐ€ ๋ชจ๋‘ ๊ฐ™์Œ. ๋ณด์•ˆ์ ์œผ๋กœ ์‹ ๊ฒฝ์“ฐ์ด๋Š” ๋ถ€๋ถ„์ด๋ผ๊ณ  ํ•˜์…จ์Œ.

 

import Foundation
import Alamofire
import CommonCrypto

enum EncryptError : Error {
    case serverErr
    case clientErr
    case encodingStringErr
}

class Encryptor {
    init() {
        
    }
    
    // testํ•  ๋•Œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
    private func test() {
        do {
            Task {
                let token = try getDigest()
            }
        }
    }
    
    // MARK: ํ†ต์‹ ์— ํ•„์š”ํ•œ value ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜
    func getDigest() throws -> String {
        do {
            let ct = try KeyChainUtil.shared.getStringTypeFromKeyChain(type: .ct)
            let ctDigest = hashString(from: appendSalt(originalValue: ct))
            let pt = try KeyChainUtil.shared.getStringTypeFromKeyChain(type: .pt)
            let result = ctDigest.appending(pt)
            if let encodedResult: String = result.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
                return encodedResult
            } else {
                throw EncryptError.encodingStringErr
            }
        } catch (let keyChainException) {
            throw keyChainException
        }
    }
    
    func fetchTokenFromServer() async throws -> String {
        return try await withCheckedThrowingContinuation { continuation in
            WebServerSignInService().serverAction(type: WebSignInModel.self) { [self] response in
                switch response {
                case .success(let data):
                    guard let data = data as? WebSignInModel else { return }
                    if let dataToken: String = data.token {
                        try? KeyChainUtil.shared.storeStringTypeInKeyChain(type: .pt, data: dataToken)
                        
                        // ์—„๋ฐ€ํžˆ ๋งํ•˜๋ฉด ์ด ์•„๋ž˜ ์ฝ”๋“œ๋“ค์€ ๋ถ„๋ฆฌ๋˜์–ด์•ผ ํ•จ. ๋ฐ›์•„์˜จ ํ† ํฐ์œผ๋กœ ํ•ด์‹ฑ๋œ ๋‹ค์ด์ œ์ŠคํŠธ ๋งŒ๋“œ๋Š” ๊ณผ์ •์ž„.
                        let digest = hashString(from: appendSalt(originalValue: "1234"))
                        let result = digest.appending(dataToken)
                        let encodedResult = result.addingPercentEncoding(withAllowedCharacters: .alphanumerics)
                        if let value: String = encodedResult {
                            continuation.resume(returning: value)
                        } else {
                            continuation.resume(throwing: EncryptError.encodingStringErr)
                        }
                        
                    } else {
                        continuation.resume(throwing: EncryptError.clientErr)
                    }
                case .pathErr(_):
                    continuation.resume(throwing: EncryptError.clientErr)
                default:
                    continuation.resume(throwing: EncryptError.serverErr)
                }
            }
        }
        
    }
    
    func appendSalt(originalValue: String) -> String {
        // ๋น„๊ณต๊ฐœ
    }
    
    func hashString(from originalValue: String) -> String {
        return originalValue.sha256()
    }
}

extension Data{
    public func sha256() -> String{
        return base64StringFromData(input: digest(input: self as NSData))
    }
    
    private func digest(input : NSData) -> NSData {
        let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
        var hash = [UInt8](repeating: 0, count: digestLength)
        CC_SHA256(input.bytes, UInt32(input.length), &hash)
        return NSData(bytes: hash, length: digestLength)
    }
    
    private  func base64StringFromData(input: NSData) -> String {
        var result = input.base64EncodedString()        
        return result
    }
}

public extension String {
    // String ํƒ€์ž…์˜ ๊ฐ’์„ NSData ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ ํ›„ SHA256 ๋ณ€ํ™˜
    func sha256() -> String {
        if let stringData = self.data(using: String.Encoding.utf8) {
            return stringData.sha256()
        }
        return ""
    }
}

์—ฌ๊ธฐ ์ฝ”๋“œ์—์„œ ๊ฒฐ๋ก ์ ์œผ๋กœ hash ๊ด€๋ จ ๋ถ€๋ถ„์€ appendsalt(), hashString(), Data.sha256(), digest(), base64StringFromData(), String.sha256() ์ด๊ฑฐ์ž„. ๋™์ž‘ ๊ณผ์ •์€ ์•„๋ž˜์™€ ๊ฐ™์Œ.

  1. String.sha256()์„ ๋ถˆ๋Ÿฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ธ์ฝ”๋”ฉํ•ด์คŒ. ์ธ์ฝ”๋”ฉํ•ด ์ค€ ๊ฒฐ๊ณผ๋ฌผ์— ๋Œ€ํ•ด Data.sha256()์„ ์‹คํ–‰
  2. Data.sha256()์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ NSData format์œผ๋กœ digest() ํ•จ์ˆ˜๋กœ ๋„˜๊น€
  3. digest๋Š” ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์œผ๋กœ SHA256 ํ•ด์‹ฑ์„ ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•จ
  4. 2์—์„œ digest()์˜ ๊ฒฐ๊ณผ๋ฌผ์— ๋Œ€ํ•ด NSData ํ˜•์‹์„ base64๋กœ ์ธ์ฝ”๋”ฉํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•จ. ์ด๋ฅผ base64StringFromData() ํ•จ์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ ์ˆ˜ํ–‰ํ•จ
  5. base64StringFromData() ํ•จ์ˆ˜์—์„œ๋Š” ๋„˜๊ฒจ๋ฐ›์€ ๊ฐ’์„ ์ธ์ฝ”๋”ฉํ•ด์คŒ. SHA256์— ์˜ํ•ด ๋งˆ์ง€๋ง‰ ๊ธ€์ž๋Š” ๊ผญ ํŒจ๋”ฉ์ด ๋“ค์–ด๊ฐ. Padding ๊ฐ’์ธ =๋ฅผ ์ œ์™ธํ•˜๊ณ  ์‹ถ์œผ๋ฉด result = String(result.dropLast()) ์ด ์ฝ”๋“œ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋จ. 
  6. 5๊นŒ์ง€ ์™„๋ฃŒํ•˜๋ฉด 2์—์„œ ์ตœ์ข… ๊ฐ’์„ ๋ฆฌํ„ดํ•ด์คŒ. 
๋”๋ณด๊ธฐ
CS/iOS, Swift